Completed
Push — master ( 903e4a...f26404 )
by Morris
11s
created

FilesHooks::addNotificationsForUser()   C

Complexity

Conditions 14
Paths 81

Size

Total Lines 42
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 14.0891

Importance

Changes 0
Metric Value
dl 0
loc 42
ccs 24
cts 26
cp 0.9231
rs 5.0864
c 0
b 0
f 0
cc 14
eloc 26
nc 81
nop 9
crap 14.0891

How to fix   Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Frank Karlitschek <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Thomas Müller <[email protected]>
8
 *
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
22
 *
23
 */
24
25
namespace OCA\Activity;
26
27
use OC\Files\Filesystem;
28
use OC\Files\View;
29
use OCA\Activity\Extension\Files;
30
use OCA\Activity\Extension\Files_Sharing;
31
use OCP\Activity\IManager;
32
use OCP\Files\Mount\IMountPoint;
33
use OCP\Files\NotFoundException;
34
use OCP\IDBConnection;
35
use OCP\IGroup;
36
use OCP\IGroupManager;
37
use OCP\ILogger;
38
use OCP\IURLGenerator;
39
use OCP\IUser;
40
use OCP\Share;
41
42
/**
43
 * The class to handle the filesystem hooks
44
 */
45
class FilesHooks {
46
	const USER_BATCH_SIZE = 50;
47
48
	/** @var \OCP\Activity\IManager */
49
	protected $manager;
50
51
	/** @var \OCA\Activity\Data */
52
	protected $activityData;
53
54
	/** @var \OCA\Activity\UserSettings */
55
	protected $userSettings;
56
57
	/** @var \OCP\IGroupManager */
58
	protected $groupManager;
59
60
	/** @var \OCP\IDBConnection */
61
	protected $connection;
62
63
	/** @var \OC\Files\View */
64
	protected $view;
65
66
	/** @var IURLGenerator */
67
	protected $urlGenerator;
68
69
	/** @var ILogger */
70
	protected $logger;
71
72
	/** @var CurrentUser */
73
	protected $currentUser;
74
75
	/** @var string|bool */
76
	protected $moveCase = false;
77
	/** @var string[] */
78
	protected $oldParentUsers;
79
	/** @var string */
80
	protected $oldParentPath;
81
	/** @var string */
82
	protected $oldParentOwner;
83
	/** @var string */
84
	protected $oldParentId;
85
86
	/**
87
	 * Constructor
88
	 *
89
	 * @param IManager $manager
90
	 * @param Data $activityData
91
	 * @param UserSettings $userSettings
92
	 * @param IGroupManager $groupManager
93
	 * @param View $view
94
	 * @param IDBConnection $connection
95
	 * @param IURLGenerator $urlGenerator
96
	 * @param ILogger $logger
97
	 * @param CurrentUser $currentUser
98
	 */
99 47
	public function __construct(IManager $manager, Data $activityData, UserSettings $userSettings, IGroupManager $groupManager, View $view, IDBConnection $connection, IURLGenerator $urlGenerator, ILogger $logger, CurrentUser $currentUser) {
100 47
		$this->manager = $manager;
101 47
		$this->activityData = $activityData;
102 47
		$this->userSettings = $userSettings;
103 47
		$this->groupManager = $groupManager;
104 47
		$this->view = $view;
105 47
		$this->connection = $connection;
106 47
		$this->urlGenerator = $urlGenerator;
107 47
		$this->logger = $logger;
108 47
		$this->currentUser = $currentUser;
109 47
	}
110
111
	/**
112
	 * Store the create hook events
113
	 * @param string $path Path of the file that has been created
114
	 */
115 2
	public function fileCreate($path) {
116 2
		if ($this->currentUser->getUserIdentifier() !== '') {
117 1
			$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CREATED, 'created_self', 'created_by');
118
		} else {
119 1
			$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CREATED, '', 'created_public');
120
		}
121 2
	}
122
123
	/**
124
	 * Store the update hook events
125
	 * @param string $path Path of the file that has been modified
126
	 */
127 1
	public function fileUpdate($path) {
128 1
		$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CHANGED, 'changed_self', 'changed_by');
129 1
	}
130
131
	/**
132
	 * Store the delete hook events
133
	 * @param string $path Path of the file that has been deleted
134
	 */
135 1
	public function fileDelete($path) {
136 1
		$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_DELETED, 'deleted_self', 'deleted_by');
137 1
	}
138
139
	/**
140
	 * Store the restore hook events
141
	 * @param string $path Path of the file that has been restored
142
	 */
143 1
	public function fileRestore($path) {
144 1
		$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_RESTORED, 'restored_self', 'restored_by');
145 1
	}
146
147
	/**
148
	 * Creates the entries for file actions on $file_path
149
	 *
150
	 * @param string $filePath         The file that is being changed
151
	 * @param int    $activityType     The activity type
152
	 * @param string $subject          The subject for the actor
153
	 * @param string $subjectBy        The subject for other users (with "by $actor")
154
	 */
155 3
	protected function addNotificationsForFileAction($filePath, $activityType, $subject, $subjectBy) {
156
		// Do not add activities for .part-files
157 3
		if (substr($filePath, -5) === '.part') {
158 1
			return;
159
		}
160
161 2
		list($filePath, $uidOwner, $fileId) = $this->getSourcePathAndOwner($filePath);
162 2
		if ($fileId === 0) {
163
			// Could not find the file for the owner ...
164
			return;
165
		}
166
167 2
		$affectedUsers = $this->getUserPathsFromPath($filePath, $uidOwner);
168 2
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'stream', $activityType);
169 2
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'email', $activityType);
170
171 2
		foreach ($affectedUsers as $user => $path) {
172 2
			$user = (string) $user;
173 2
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
174 2
				continue;
175
			}
176
177 2
			if ($user === $this->currentUser->getUID()) {
178 1
				$userSubject = $subject;
179 1
				$userParams = [[$fileId => $path]];
180
			} else {
181 1
				$userSubject = $subjectBy;
182 1
				$userParams = [[$fileId => $path], $this->currentUser->getUserIdentifier()];
183
			}
184
185 2
			$this->addNotificationsForUser(
186
				$user, $userSubject, $userParams,
187 2
				$fileId, $path, true,
188 2
				!empty($filteredStreamUsers[$user]),
189 2
				!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
190
				$activityType
191
			);
192
		}
193 2
	}
194
195
	/**
196
	 * Collect some information for move/renames
197
	 *
198
	 * @param string $oldPath Path of the file that has been moved
199
	 * @param string $newPath Path of the file that has been moved
200
	 */
201
	public function fileMove($oldPath, $newPath) {
202
		if (substr($oldPath, -5) === '.part' || substr($newPath, -5) === '.part') {
203
			// Do not add activities for .part-files
204
			$this->moveCase = false;
205
			return;
206
		}
207
208
		$oldDir = dirname($oldPath);
209
		$newDir = dirname($newPath);
210
211
		if ($oldDir === $newDir) {
212
			/**
213
			 * a/b moved to a/c
214
			 *
215
			 * Cases:
216
			 * - a/b shared: no visible change
217
			 * - a/ shared: rename
218
			 */
219
			$this->moveCase = 'rename';
220
			return;
221
		}
222
223
		if (strpos($oldDir, $newDir) === 0) {
224
			/**
225
			 * a/b/c moved to a/c
226
			 *
227
			 * Cases:
228
			 * - a/b/c shared: no visible change
229
			 * - a/b/ shared: delete
230
			 * - a/ shared: move/rename
231
			 */
232
			$this->moveCase = 'moveUp';
233
		} else if (strpos($newDir, $oldDir) === 0) {
234
			/**
235
			 * a/b moved to a/c/b
236
			 *
237
			 * Cases:
238
			 * - a/b shared: no visible change
239
			 * - a/c/ shared: add
240
			 * - a/ shared: move/rename
241
			 */
242
			$this->moveCase = 'moveDown';
243
		} else {
244
			/**
245
			 * a/b/c moved to a/d/c
246
			 *
247
			 * Cases:
248
			 * - a/b/c shared: no visible change
249
			 * - a/b/ shared: delete
250
			 * - a/d/ shared: add
251
			 * - a/ shared: move/rename
252
			 */
253
			$this->moveCase = 'moveCross';
254
		}
255
256
		list($this->oldParentPath, $this->oldParentOwner, $this->oldParentId) = $this->getSourcePathAndOwner($oldDir);
257
		if ($this->oldParentId === 0) {
258
			// Could not find the file for the owner ...
259
			$this->moveCase = false;
260
			return;
261
		}
262
		$this->oldParentUsers = $this->getUserPathsFromPath($this->oldParentPath, $this->oldParentOwner);
263
	}
264
265
266
	/**
267
	 * Store the move hook events
268
	 *
269
	 * @param string $oldPath Path of the file that has been moved
270
	 * @param string $newPath Path of the file that has been moved
271
	 */
272
	public function fileMovePost($oldPath, $newPath) {
273
		// Do not add activities for .part-files
274
		if ($this->moveCase === false) {
275
			return;
276
		}
277
278
		switch ($this->moveCase) {
279
			case 'rename':
280
				$this->fileRenaming($oldPath, $newPath);
281
				break;
282
			case 'moveUp':
283
			case 'moveDown':
284
			case 'moveCross':
285
				$this->fileMoving($oldPath, $newPath);
286
				break;
287
		}
288
289
		$this->moveCase = false;
290
	}
291
292
293
	/**
294
	 * Renaming a file inside the same folder (a/b to a/c)
295
	 *
296
	 * @param string $oldPath
297
	 * @param string $newPath
298
	 */
299
	protected function fileRenaming($oldPath, $newPath) {
300
		$dirName = dirname($newPath);
301
		$fileName = basename($newPath);
302
		$oldFileName = basename($oldPath);
303
304
		list(, , $fileId) = $this->getSourcePathAndOwner($newPath);
305
		list($parentPath, $parentOwner, $parentId) = $this->getSourcePathAndOwner($dirName);
306
		if ($fileId === 0 || $parentId === 0) {
307
			// Could not find the file for the owner ...
308
			return;
309
		}
310
		$affectedUsers = $this->getUserPathsFromPath($parentPath, $parentOwner);
311
312
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'stream', Files::TYPE_SHARE_CHANGED);
313
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'email', Files::TYPE_SHARE_CHANGED);
314
315
		foreach ($affectedUsers as $user => $path) {
316
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
317
				continue;
318
			}
319
320
			if ($user === $this->currentUser->getUID()) {
321
				$userSubject = 'renamed_self';
322
				$userParams = [
323
					[$fileId => $path . '/' . $fileName],
324
					[$fileId => $path . '/' . $oldFileName],
325
				];
326
			} else {
327
				$userSubject = 'renamed_by';
328
				$userParams = [
329
					[$fileId => $path . '/' . $fileName],
330
					$this->currentUser->getUserIdentifier(),
331
					[$fileId => $path . '/' . $oldFileName],
332
				];
333
			}
334
335
			$this->addNotificationsForUser(
336
				$user, $userSubject, $userParams,
337
				$fileId, $path . '/' . $fileName, true,
338
				!empty($filteredStreamUsers[$user]),
339
				!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
340
				Files::TYPE_SHARE_CHANGED
341
			);
342
		}
343
	}
344
345
	/**
346
	 * Moving a file from one folder to another
347
	 *
348
	 * @param string $oldPath
349
	 * @param string $newPath
350
	 */
351
	protected function fileMoving($oldPath, $newPath) {
352
		$dirName = dirname($newPath);
353
		$fileName = basename($newPath);
354
		$oldFileName = basename($oldPath);
355
356
		list(, , $fileId) = $this->getSourcePathAndOwner($newPath);
357
		list($parentPath, $parentOwner, $parentId) = $this->getSourcePathAndOwner($dirName);
358
		if ($fileId === 0 || $parentId === 0) {
359
			// Could not find the file for the owner ...
360
			return;
361
		}
362
		$affectedUsers = $this->getUserPathsFromPath($parentPath, $parentOwner);
363
364
		$beforeUsers = array_keys($this->oldParentUsers);
365
		$afterUsers = array_keys($affectedUsers);
366
367
		$deleteUsers = array_diff($beforeUsers, $afterUsers);
368
		$this->generateDeleteActivities($deleteUsers, $this->oldParentUsers, $fileId, $oldFileName);
369
370
		$addUsers = array_diff($afterUsers, $beforeUsers);
371
		$this->generateAddActivities($addUsers, $affectedUsers, $fileId, $fileName);
372
373
		$moveUsers = array_intersect($beforeUsers, $afterUsers);
374
		$this->generateMoveActivities($moveUsers, $this->oldParentUsers, $affectedUsers, $fileId, $oldFileName, $parentId, $fileName);
375
	}
376
377
	/**
378
	 * @param string[] $users
379
	 * @param string[] $pathMap
380
	 * @param int $fileId
381
	 * @param string $oldFileName
382
	 */
383 View Code Duplication
	protected function generateDeleteActivities($users, $pathMap, $fileId, $oldFileName) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
384
		if (empty($users)) {
385
			return;
386
		}
387
388
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting($users, 'stream', Files::TYPE_SHARE_DELETED);
389
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', Files::TYPE_SHARE_DELETED);
390
391
		foreach ($users as $user) {
392
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
393
				continue;
394
			}
395
396
			$path = $pathMap[$user];
397
398
			if ($user === $this->currentUser->getUID()) {
399
				$userSubject = 'deleted_self';
400
				$userParams = [[$fileId => $path . '/' . $oldFileName]];
401
			} else {
402
				$userSubject = 'deleted_by';
403
				$userParams = [[$fileId => $path . '/' . $oldFileName], $this->currentUser->getUserIdentifier()];
404
			}
405
406
			$this->addNotificationsForUser(
407
				$user, $userSubject, $userParams,
408
				$fileId, $path . '/' . $oldFileName, true,
409
				!empty($filteredStreamUsers[$user]),
410
				!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
411
				Files::TYPE_SHARE_DELETED
412
			);
413
		}
414
	}
415
416
	/**
417
	 * @param string[] $users
418
	 * @param string[] $pathMap
419
	 * @param int $fileId
420
	 * @param string $fileName
421
	 */
422 View Code Duplication
	protected function generateAddActivities($users, $pathMap, $fileId, $fileName) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
423
		if (empty($users)) {
424
			return;
425
		}
426
427
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting($users, 'stream', Files::TYPE_SHARE_CREATED);
428
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', Files::TYPE_SHARE_CREATED);
429
430
		foreach ($users as $user) {
431
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
432
				continue;
433
			}
434
435
			$path = $pathMap[$user];
436
437
			if ($user === $this->currentUser->getUID()) {
438
				$userSubject = 'created_self';
439
				$userParams = [[$fileId => $path . '/' . $fileName]];
440
			} else {
441
				$userSubject = 'created_by';
442
				$userParams = [[$fileId => $path . '/' . $fileName], $this->currentUser->getUserIdentifier()];
443
			}
444
445
			$this->addNotificationsForUser(
446
				$user, $userSubject, $userParams,
447
				$fileId, $path . '/' . $fileName, true,
448
				!empty($filteredStreamUsers[$user]),
449
				!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
450
				Files::TYPE_SHARE_CREATED
451
			);
452
		}
453
	}
454
455
	/**
456
	 * @param string[] $users
457
	 * @param string[] $beforePathMap
458
	 * @param string[] $afterPathMap
459
	 * @param int $fileId
460
	 * @param string $oldFileName
461
	 * @param int $newParentId
462
	 * @param string $fileName
463
	 */
464
	protected function generateMoveActivities($users, $beforePathMap, $afterPathMap, $fileId, $oldFileName, $newParentId, $fileName) {
465
		if (empty($users)) {
466
			return;
467
		}
468
469
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting($users, 'stream', Files::TYPE_SHARE_CHANGED);
470
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', Files::TYPE_SHARE_CHANGED);
471
472
		foreach ($users as $user) {
473
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
474
				continue;
475
			}
476
477
			if ($oldFileName === $fileName) {
478
				$userParams = [[$newParentId => $afterPathMap[$user] . '/']];
479
			} else {
480
				$userParams = [[$fileId => $afterPathMap[$user] . '/' . $fileName]];
481
			}
482
483
			if ($user === $this->currentUser->getUID()) {
484
				$userSubject = 'moved_self';
485
			} else {
486
				$userSubject = 'moved_by';
487
				$userParams[] = $this->currentUser->getUserIdentifier();
488
			}
489
			$userParams[] = [$fileId => $beforePathMap[$user] . '/' . $oldFileName];
490
491
			$this->addNotificationsForUser(
492
				$user, $userSubject, $userParams,
493
				$fileId, $afterPathMap[$user] . '/' . $fileName, true,
494
				!empty($filteredStreamUsers[$user]),
495
				!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
496
				Files::TYPE_SHARE_CHANGED
497
			);
498
		}
499
	}
500
501
	/**
502
	 * Returns a "username => path" map for all affected users
503
	 *
504
	 * @param string $path
505
	 * @param string $uidOwner
506
	 * @return array
507
	 */
508
	protected function getUserPathsFromPath($path, $uidOwner) {
509
		return Share::getUsersSharingFile($path, $uidOwner, true, true);
510
	}
511
512
	/**
513
	 * Return the source
514
	 *
515
	 * @param string $path
516
	 * @return array
517
	 */
518
	protected function getSourcePathAndOwner($path) {
519
		$view = Filesystem::getView();
520
		$uidOwner = $view->getOwner($path);
521
		$fileId = 0;
522
523
		if ($uidOwner !== $this->currentUser->getUID()) {
524
			/** @var \OCP\Files\Storage\IStorage $storage */
525
			list($storage,) = $view->resolvePath($path);
526
			if (!$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
527
				Filesystem::initMountPoints($uidOwner);
528
			} else {
529
				// Probably a remote user, let's try to at least generate activities
530
				// for the current user
531
				$uidOwner = $this->currentUser->getUID();
532
			}
533
		}
534
535
		$info = Filesystem::getFileInfo($path);
536
		if ($info !== false) {
537
			$ownerView = new View('/' . $uidOwner . '/files');
538
			$fileId = (int) $info['fileid'];
539
			$path = $ownerView->getPath($fileId);
540
		}
541
542
		return array($path, $uidOwner, $fileId);
543
	}
544
545
	/**
546
	 * Manage sharing events
547
	 * @param array $params The hook params
548
	 */
549 3 View Code Duplication
	public function share($params) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
550 3
		if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
551 3
			if ((int) $params['shareType'] === Share::SHARE_TYPE_USER) {
552 1
				$this->shareFileOrFolderWithUser($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], true);
553 2
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_GROUP) {
554 1
				$this->shareFileOrFolderWithGroup($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], (int) $params['id'], true);
555 1
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_LINK) {
556 1
				$this->shareFileOrFolderByLink((int) $params['fileSource'], $params['itemType'], $params['uidOwner'], true);
557
			}
558
		}
559 3
	}
560
561
	/**
562
	 * Manage sharing events
563
	 * @param array $params The hook params
564
	 */
565 View Code Duplication
	public function unShare($params) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
566
		if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
567
			if ((int) $params['shareType'] === Share::SHARE_TYPE_USER) {
568
				$this->shareFileOrFolderWithUser($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], false);
569
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_GROUP) {
570
				$this->shareFileOrFolderWithGroup($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], (int) $params['id'], false);
571
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_LINK) {
572
				$this->shareFileOrFolderByLink((int) $params['fileSource'], $params['itemType'], $params['uidOwner'], false);
573
			}
574
		}
575
	}
576
577
	/**
578
	 * Sharing a file or folder with a user
579
	 *
580
	 * @param string $shareWith
581
	 * @param int $fileSource File ID that is being shared
582
	 * @param string $itemType File type that is being shared (file or folder)
583
	 * @param string $fileTarget File path
584
	 * @param bool $isSharing True if sharing, false if unsharing
585
	 */
586 2
	protected function shareFileOrFolderWithUser($shareWith, $fileSource, $itemType, $fileTarget, $isSharing) {
587 2
		if ($isSharing) {
588 2
			$actionSharer = 'shared_user_self';
589 2
			$actionOwner = 'reshared_user_by';
590 2
			$actionUser = 'shared_with_by';
591
		} else {
592
			$actionSharer = 'unshared_user_self';
593
			$actionOwner = 'unshared_user_by';
594
			$actionUser = 'unshared_by';
595
		}
596
597
		// User performing the share
598 2
		$this->shareNotificationForSharer($actionSharer, $shareWith, $fileSource, $itemType);
599 2 View Code Duplication
		if ($this->currentUser->getUID() !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
600 2
			$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), $actionOwner, $shareWith, $fileSource, $itemType);
601
		}
602
603
		// New shared user
604 2
		$this->addNotificationsForUser(
605 2
			$shareWith, $actionUser, [[$fileSource => $fileTarget], $this->currentUser->getUserIdentifier()],
606 2
			(int) $fileSource, $fileTarget, ($itemType === 'file'),
607 2
			$this->userSettings->getUserSetting($shareWith, 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
Bug introduced by
It seems like $this->userSettings->get...s_Sharing::TYPE_SHARED) targeting OCA\Activity\UserSettings::getUserSetting() can also be of type integer; however, OCA\Activity\FilesHooks::addNotificationsForUser() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
608 2
			$this->userSettings->getUserSetting($shareWith, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($shareWith, 'setting', 'batchtime') : 0
0 ignored issues
show
Bug introduced by
It seems like $this->userSettings->get...ting', 'batchtime') : 0 can also be of type boolean; however, OCA\Activity\FilesHooks::addNotificationsForUser() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
609
		);
610 2
	}
611
612
	/**
613
	 * Sharing a file or folder with a group
614
	 *
615
	 * @param string $shareWith
616
	 * @param int $fileSource File ID that is being shared
617
	 * @param string $itemType File type that is being shared (file or folder)
618
	 * @param string $fileTarget File path
619
	 * @param int $shareId The Share ID of this share
620
	 * @param bool $isSharing True if sharing, false if unsharing
621
	 */
622 6
	protected function shareFileOrFolderWithGroup($shareWith, $fileSource, $itemType, $fileTarget, $shareId, $isSharing) {
623 6
		if ($isSharing) {
624 6
			$actionSharer = 'shared_group_self';
625 6
			$actionOwner = 'reshared_group_by';
626 6
			$actionUser = 'shared_with_by';
627
		} else {
628
			$actionSharer = 'unshared_group_self';
629
			$actionOwner = 'unshared_group_by';
630
			$actionUser = 'unshared_by';
631
		}
632
633
		// Members of the new group
634 6
		$group = $this->groupManager->get($shareWith);
635 6
		if (!($group instanceof IGroup)) {
0 ignored issues
show
Bug introduced by
The class OCP\IGroup does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
636 1
			return;
637
		}
638
639
		// User performing the share
640 5
		$this->shareNotificationForSharer($actionSharer, $shareWith, $fileSource, $itemType);
641 5 View Code Duplication
		if ($this->currentUser->getUID() !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
642 5
			$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), $actionOwner, $shareWith, $fileSource, $itemType);
643
		}
644
645 5
		$offset = 0;
646 5
		$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
647 5
		while (!empty($users)) {
648 4
			$this->addNotificationsForGroupUsers($users, $actionUser, $fileSource, $itemType, $fileTarget, $shareId);
649 4
			$offset += self::USER_BATCH_SIZE;
650 4
			$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
651
		}
652 5
	}
653
654
	/**
655
	 * @param IUser[] $usersInGroup
656
	 * @param string $actionUser
657
	 * @param int $fileSource File ID that is being shared
658
	 * @param string $itemType File type that is being shared (file or folder)
659
	 * @param string $fileTarget File path
660
	 * @param int $shareId The Share ID of this share
661
	 */
662 4
	protected function addNotificationsForGroupUsers(array $usersInGroup, $actionUser, $fileSource, $itemType, $fileTarget, $shareId) {
663 4
		$affectedUsers = [];
664
665 4
		foreach ($usersInGroup as $user) {
666 4
			$affectedUsers[$user->getUID()] = $fileTarget;
667
		}
668
669
		// Remove the triggering user, we already managed his notifications
670 4
		unset($affectedUsers[$this->currentUser->getUID()]);
671
672 4
		if (empty($affectedUsers)) {
673 1
			return;
674
		}
675
676 3
		$userIds = array_keys($affectedUsers);
677 3
		$filteredStreamUsersInGroup = $this->userSettings->filterUsersBySetting($userIds, 'stream', Files_Sharing::TYPE_SHARED);
678 3
		$filteredEmailUsersInGroup = $this->userSettings->filterUsersBySetting($userIds, 'email', Files_Sharing::TYPE_SHARED);
679
680 3
		$affectedUsers = $this->fixPathsForShareExceptions($affectedUsers, $shareId);
681 3
		foreach ($affectedUsers as $user => $path) {
682 3
			if (empty($filteredStreamUsersInGroup[$user]) && empty($filteredEmailUsersInGroup[$user])) {
683 2
				continue;
684
			}
685
686 1
			$this->addNotificationsForUser(
687 1
				$user, $actionUser, [[$fileSource => $path], $this->currentUser->getUserIdentifier()],
688 1
				$fileSource, $path, ($itemType === 'file'),
689 1
				!empty($filteredStreamUsersInGroup[$user]),
690 1
				!empty($filteredEmailUsersInGroup[$user]) ? $filteredEmailUsersInGroup[$user] : 0
691
			);
692
		}
693 3
	}
694
695
	/**
696
	 * Check when there was a naming conflict and the target is different
697
	 * for some of the users
698
	 *
699
	 * @param array $affectedUsers
700
	 * @param int $shareId
701
	 * @return mixed
702
	 */
703
	protected function fixPathsForShareExceptions(array $affectedUsers, $shareId) {
704
		$queryBuilder = $this->connection->getQueryBuilder();
705
		$queryBuilder->select(['share_with', 'file_target'])
706
			->from('share')
707
			->where($queryBuilder->expr()->eq('parent', $queryBuilder->createParameter('parent')))
708
			->setParameter('parent', (int) $shareId);
709
		$query = $queryBuilder->execute();
710
711
		while ($row = $query->fetch()) {
712
			$affectedUsers[$row['share_with']] = $row['file_target'];
713
		}
714
715
		return $affectedUsers;
716
	}
717
718
	/**
719
	 * Sharing a file or folder via link/public
720
	 *
721
	 * @param int $fileSource File ID that is being shared
722
	 * @param string $itemType File type that is being shared (file or folder)
723
	 * @param string $linkOwner
724
	 * @param bool $isSharing True if sharing, false if unsharing
725
	 */
726 2
	protected function shareFileOrFolderByLink($fileSource, $itemType, $linkOwner, $isSharing) {
727 2
		$owner = $this->currentUser->getUID();
728 2
		if ($isSharing) {
729 2
			$actionSharer = 'shared_link_self';
730 2
			$actionOwner = 'reshared_link_by';
731
		} else if ($owner !== $linkOwner) {
732
			// Link expired
733
			$actionSharer = 'link_expired';
734
			$actionOwner = 'link_by_expired';
735
			$owner = $linkOwner;
736
			\OC::$server->getUserFolder($linkOwner);
737
		} else {
738
			$actionSharer = 'unshared_link_self';
739
			$actionOwner = 'unshared_link_by';
740
		}
741
742 2
		$this->view->chroot('/' . $owner . '/files');
743
744
		try {
745 2
			$path = $this->view->getPath($fileSource);
746 1
		} catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
747 1
			return;
748
		}
749
750 1
		$this->shareNotificationForOriginalOwners($owner, $actionOwner, '', $fileSource, $itemType);
751
752 1
		$this->addNotificationsForUser(
753 1
			$owner, $actionSharer, [[$fileSource => $path]],
754 1
			(int) $fileSource, $path, ($itemType === 'file'),
755 1
			$this->userSettings->getUserSetting($owner, 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
Bug introduced by
It seems like $this->userSettings->get...s_Sharing::TYPE_SHARED) targeting OCA\Activity\UserSettings::getUserSetting() can also be of type integer; however, OCA\Activity\FilesHooks::addNotificationsForUser() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
756 1
			$this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : 0
0 ignored issues
show
Bug introduced by
It seems like $this->userSettings->get...ting', 'batchtime') : 0 can also be of type boolean; however, OCA\Activity\FilesHooks::addNotificationsForUser() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
757
		);
758 1
	}
759
760
	/**
761
	 * Add notifications for the user that shares a file/folder
762
	 *
763
	 * @param string $subject
764
	 * @param string $shareWith
765
	 * @param int $fileSource
766
	 * @param string $itemType
767
	 */
768 2
	protected function shareNotificationForSharer($subject, $shareWith, $fileSource, $itemType) {
769 2
		$sharer = $this->currentUser->getUID();
770 2
		if ($sharer === null) {
771
			return;
772
		}
773
774 2
		$this->view->chroot('/' . $sharer . '/files');
775
776
		try {
777 2
			$path = $this->view->getPath($fileSource);
778 1
		} catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
779 1
			return;
780
		}
781
782 1
		$this->addNotificationsForUser(
783 1
			$sharer, $subject, [[$fileSource => $path], $shareWith],
784 1
			$fileSource, $path, ($itemType === 'file'),
785 1
			$this->userSettings->getUserSetting($sharer, 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
Bug introduced by
It seems like $this->userSettings->get...s_Sharing::TYPE_SHARED) targeting OCA\Activity\UserSettings::getUserSetting() can also be of type integer; however, OCA\Activity\FilesHooks::addNotificationsForUser() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
786 1
			$this->userSettings->getUserSetting($sharer, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($sharer, 'setting', 'batchtime') : 0
0 ignored issues
show
Bug introduced by
It seems like $this->userSettings->get...ting', 'batchtime') : 0 can also be of type boolean; however, OCA\Activity\FilesHooks::addNotificationsForUser() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
787
		);
788 1
	}
789
790
	/**
791
	 * Add notifications for the user that shares a file/folder
792
	 *
793
	 * @param string $owner
794
	 * @param string $subject
795
	 * @param string $shareWith
796
	 * @param int $fileSource
797
	 * @param string $itemType
798
	 */
799 2
	protected function reshareNotificationForSharer($owner, $subject, $shareWith, $fileSource, $itemType) {
800 2
		$this->view->chroot('/' . $owner . '/files');
801
802
		try {
803 2
			$path = $this->view->getPath($fileSource);
804 1
		} catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
805 1
			return;
806
		}
807
808 1
		$this->addNotificationsForUser(
809 1
			$owner, $subject, [[$fileSource => $path], $this->currentUser->getUserIdentifier(), $shareWith],
810 1
			$fileSource, $path, ($itemType === 'file'),
811 1
			$this->userSettings->getUserSetting($owner, 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
Bug introduced by
It seems like $this->userSettings->get...s_Sharing::TYPE_SHARED) targeting OCA\Activity\UserSettings::getUserSetting() can also be of type integer; however, OCA\Activity\FilesHooks::addNotificationsForUser() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
812 1
			$this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : 0
0 ignored issues
show
Bug introduced by
It seems like $this->userSettings->get...ting', 'batchtime') : 0 can also be of type boolean; however, OCA\Activity\FilesHooks::addNotificationsForUser() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
813
		);
814 1
	}
815
816
	/**
817
	 * Add notifications for the owners whose files have been reshared
818
	 *
819
	 * @param string $currentOwner
820
	 * @param string $subject
821
	 * @param string $shareWith
822
	 * @param int $fileSource
823
	 * @param string $itemType
824
	 */
825 10
	protected function shareNotificationForOriginalOwners($currentOwner, $subject, $shareWith, $fileSource, $itemType) {
826
		// Get the full path of the current user
827 10
		$this->view->chroot('/' . $currentOwner . '/files');
828
829
		try {
830 10
			$path = $this->view->getPath($fileSource);
831 1
		} catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
832 1
			return;
833
		}
834
835
		/**
836
		 * Get the original owner and his path
837
		 */
838 9
		$owner = $this->view->getOwner($path);
839 9
		if ($owner !== $currentOwner) {
840 7
			$this->reshareNotificationForSharer($owner, $subject, $shareWith, $fileSource, $itemType);
841
		}
842
843
		/**
844
		 * Get the sharee who shared the item with the currentUser
845
		 */
846 9
		$this->view->chroot('/' . $currentOwner . '/files');
847 9
		$mount = $this->view->getMount($path);
848 9
		if (!($mount instanceof IMountPoint)) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\Mount\IMountPoint does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
849 1
			return;
850
		}
851
852 8
		$storage = $mount->getStorage();
853 8
		if (!$storage->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) {
854 1
			return;
855
		}
856
857
		/** @var \OCA\Files_Sharing\SharedStorage $storage */
858 7
		$shareOwner = $storage->getSharedFrom();
859 7
		if ($shareOwner === '' || $shareOwner === null || $shareOwner === $owner || $shareOwner === $currentOwner) {
860 5
			return;
861
		}
862
863 2
		$this->reshareNotificationForSharer($shareOwner, $subject, $shareWith, $fileSource, $itemType);
864 2
	}
865
866
	/**
867
	 * Adds the activity and email for a user when the settings require it
868
	 *
869
	 * @param string $user
870
	 * @param string $subject
871
	 * @param array $subjectParams
872
	 * @param int $fileId
873
	 * @param string $path
874
	 * @param bool $isFile If the item is a file, we link to the parent directory
875
	 * @param bool $streamSetting
876
	 * @param int $emailSetting
877
	 * @param string $type
878
	 */
879 11
	protected function addNotificationsForUser($user, $subject, $subjectParams, $fileId, $path, $isFile, $streamSetting, $emailSetting, $type = Files_Sharing::TYPE_SHARED) {
880 11
		if (!$streamSetting && !$emailSetting) {
881 1
			return;
882
		}
883
884 10
		$selfAction = $user === $this->currentUser->getUID();
885 10
		$app = $type === Files_Sharing::TYPE_SHARED ? 'files_sharing' : 'files';
886 10
		$link = $this->urlGenerator->linkToRouteAbsolute('files.view.index', array(
887 10
			'dir' => ($isFile) ? dirname($path) : $path,
888
		));
889
890 10
		$objectType = ($fileId) ? 'files' : '';
891
892 10
		$event = $this->manager->generateEvent();
893
		try {
894 10
			$event->setApp($app)
895 10
				->setType($type)
896 10
				->setAffectedUser($user)
897 10
				->setTimestamp(time())
898 10
				->setSubject($subject, $subjectParams)
899 10
				->setObject($objectType, $fileId, $path)
900 10
				->setLink($link);
901
902 10
			if ($this->currentUser->getUID() !== null) {
903
				// Allow this to be empty for guests
904 10
				$event->setAuthor($this->currentUser->getUID());
905
			}
906
		} catch (\InvalidArgumentException $e) {
907
			$this->logger->logException($e);
908
		}
909
910
		// Add activity to stream
911 10
		if ($streamSetting && (!$selfAction || $this->userSettings->getUserSetting($this->currentUser->getUID(), 'setting', 'self'))) {
912 3
			$this->activityData->send($event);
913
		}
914
915
		// Add activity to mail queue
916 10
		if ($emailSetting && (!$selfAction || $this->userSettings->getUserSetting($this->currentUser->getUID(), 'setting', 'selfemail'))) {
917 5
			$latestSend = time() + $emailSetting;
918 5
			$this->activityData->storeMail($event, $latestSend);
919
		}
920 10
	}
921
}
922