Completed
Push — master ( 2ec663...9eff94 )
by Joas
02:30
created

FilesHooks::unShare()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 11
Code Lines 8

Duplication

Lines 11
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 11
loc 11
ccs 0
cts 9
cp 0
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 8
nc 5
nop 1
crap 42
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\IURLGenerator;
38
use OCP\IUser;
39
use OCP\Share;
40
41
/**
42
 * The class to handle the filesystem hooks
43
 */
44
class FilesHooks {
45
	const USER_BATCH_SIZE = 50;
46
47
	/** @var \OCP\Activity\IManager */
48
	protected $manager;
49
50
	/** @var \OCA\Activity\Data */
51
	protected $activityData;
52
53
	/** @var \OCA\Activity\UserSettings */
54
	protected $userSettings;
55
56
	/** @var \OCP\IGroupManager */
57
	protected $groupManager;
58
59
	/** @var \OCP\IDBConnection */
60
	protected $connection;
61
62
	/** @var \OC\Files\View */
63
	protected $view;
64
65
	/** @var IURLGenerator */
66
	protected $urlGenerator;
67
68
	/** @var CurrentUser */
69
	protected $currentUser;
70
71
	/** @var string|bool */
72
	protected $moveCase = false;
73
	/** @var string[] */
74
	protected $oldParentUsers;
75
	/** @var string */
76
	protected $oldParentPath;
77
	/** @var string */
78
	protected $oldParentOwner;
79
	/** @var string */
80
	protected $oldParentId;
81
82
	/**
83
	 * Constructor
84
	 *
85
	 * @param IManager $manager
86
	 * @param Data $activityData
87
	 * @param UserSettings $userSettings
88
	 * @param IGroupManager $groupManager
89
	 * @param View $view
90
	 * @param IDBConnection $connection
91
	 * @param IURLGenerator $urlGenerator
92
	 * @param CurrentUser $currentUser
93
	 */
94 47 View Code Duplication
	public function __construct(IManager $manager, Data $activityData, UserSettings $userSettings, IGroupManager $groupManager, View $view, IDBConnection $connection, IURLGenerator $urlGenerator, CurrentUser $currentUser) {
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...
95 47
		$this->manager = $manager;
96 47
		$this->activityData = $activityData;
97 47
		$this->userSettings = $userSettings;
98 47
		$this->groupManager = $groupManager;
99 47
		$this->view = $view;
100 47
		$this->connection = $connection;
101 47
		$this->urlGenerator = $urlGenerator;
102 47
		$this->currentUser = $currentUser;
103 47
	}
104
105
	/**
106
	 * Store the create hook events
107
	 * @param string $path Path of the file that has been created
108
	 */
109 2
	public function fileCreate($path) {
110 2
		if ($this->currentUser->getUserIdentifier() !== '') {
111 1
			$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CREATED, 'created_self', 'created_by');
112
		} else {
113 1
			$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CREATED, '', 'created_public');
114
		}
115 2
	}
116
117
	/**
118
	 * Store the update hook events
119
	 * @param string $path Path of the file that has been modified
120
	 */
121 1
	public function fileUpdate($path) {
122 1
		$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CHANGED, 'changed_self', 'changed_by');
123 1
	}
124
125
	/**
126
	 * Store the delete hook events
127
	 * @param string $path Path of the file that has been deleted
128
	 */
129 1
	public function fileDelete($path) {
130 1
		$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_DELETED, 'deleted_self', 'deleted_by');
131 1
	}
132
133
	/**
134
	 * Store the restore hook events
135
	 * @param string $path Path of the file that has been restored
136
	 */
137 1
	public function fileRestore($path) {
138 1
		$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_RESTORED, 'restored_self', 'restored_by');
139 1
	}
140
141
	/**
142
	 * Creates the entries for file actions on $file_path
143
	 *
144
	 * @param string $filePath         The file that is being changed
145
	 * @param int    $activityType     The activity type
146
	 * @param string $subject          The subject for the actor
147
	 * @param string $subjectBy        The subject for other users (with "by $actor")
148
	 */
149 3
	protected function addNotificationsForFileAction($filePath, $activityType, $subject, $subjectBy) {
150
		// Do not add activities for .part-files
151 3
		if (substr($filePath, -5) === '.part') {
152 1
			return;
153
		}
154
155 2
		list($filePath, $uidOwner, $fileId) = $this->getSourcePathAndOwner($filePath);
156 2
		if ($fileId === 0) {
157
			// Could not find the file for the owner ...
158
			return;
159
		}
160
161 2
		$affectedUsers = $this->getUserPathsFromPath($filePath, $uidOwner);
162 2
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'stream', $activityType);
163 2
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'email', $activityType);
164
165 2
		foreach ($affectedUsers as $user => $path) {
166 2
			$user = (string) $user;
167 2
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
168 2
				continue;
169
			}
170
171 2
			if ($user === $this->currentUser->getUID()) {
172 1
				$userSubject = $subject;
173 1
				$userParams = [[$fileId => $path]];
174
			} else {
175 1
				$userSubject = $subjectBy;
176 1
				$userParams = [[$fileId => $path], $this->currentUser->getUserIdentifier()];
177
			}
178
179 2
			$this->addNotificationsForUser(
180
				$user, $userSubject, $userParams,
181 2
				$fileId, $path, true,
182 2
				!empty($filteredStreamUsers[$user]),
183 2
				!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
184
				$activityType
185
			);
186
		}
187 2
	}
188
189
	/**
190
	 * Collect some information for move/renames
191
	 *
192
	 * @param string $oldPath Path of the file that has been moved
193
	 * @param string $newPath Path of the file that has been moved
194
	 */
195
	public function fileMove($oldPath, $newPath) {
196
		if (substr($oldPath, -5) === '.part' || substr($newPath, -5) === '.part') {
197
			// Do not add activities for .part-files
198
			$this->moveCase = false;
199
			return;
200
		}
201
202
		$oldDir = dirname($oldPath);
203
		$newDir = dirname($newPath);
204
205
		if ($oldDir === $newDir) {
206
			/**
207
			 * a/b moved to a/c
208
			 *
209
			 * Cases:
210
			 * - a/b shared: no visible change
211
			 * - a/ shared: rename
212
			 */
213
			$this->moveCase = 'rename';
214
			return;
215
		}
216
217
		if (strpos($oldDir, $newDir) === 0) {
218
			/**
219
			 * a/b/c moved to a/c
220
			 *
221
			 * Cases:
222
			 * - a/b/c shared: no visible change
223
			 * - a/b/ shared: delete
224
			 * - a/ shared: move/rename
225
			 */
226
			$this->moveCase = 'moveUp';
227
		} else if (strpos($newDir, $oldDir) === 0) {
228
			/**
229
			 * a/b moved to a/c/b
230
			 *
231
			 * Cases:
232
			 * - a/b shared: no visible change
233
			 * - a/c/ shared: add
234
			 * - a/ shared: move/rename
235
			 */
236
			$this->moveCase = 'moveDown';
237
		} else {
238
			/**
239
			 * a/b/c moved to a/d/c
240
			 *
241
			 * Cases:
242
			 * - a/b/c shared: no visible change
243
			 * - a/b/ shared: delete
244
			 * - a/d/ shared: add
245
			 * - a/ shared: move/rename
246
			 */
247
			$this->moveCase = 'moveCross';
248
		}
249
250
		list($this->oldParentPath, $this->oldParentOwner, $this->oldParentId) = $this->getSourcePathAndOwner($oldDir);
251
		if ($this->oldParentId === 0) {
252
			// Could not find the file for the owner ...
253
			$this->moveCase = false;
254
			return;
255
		}
256
		$this->oldParentUsers = $this->getUserPathsFromPath($this->oldParentPath, $this->oldParentOwner);
257
	}
258
259
260
	/**
261
	 * Store the move hook events
262
	 *
263
	 * @param string $oldPath Path of the file that has been moved
264
	 * @param string $newPath Path of the file that has been moved
265
	 */
266
	public function fileMovePost($oldPath, $newPath) {
267
		// Do not add activities for .part-files
268
		if ($this->moveCase === false) {
269
			return;
270
		}
271
272
		switch ($this->moveCase) {
273
			case 'rename':
274
				$this->fileRenaming($oldPath, $newPath);
275
				break;
276
			case 'moveUp':
277
			case 'moveDown':
278
			case 'moveCross':
279
				$this->fileMoving($oldPath, $newPath);
280
				break;
281
		}
282
283
		$this->moveCase = false;
284
	}
285
286
287
	/**
288
	 * Renaming a file inside the same folder (a/b to a/c)
289
	 *
290
	 * @param string $oldPath
291
	 * @param string $newPath
292
	 */
293
	protected function fileRenaming($oldPath, $newPath) {
294
		$dirName = dirname($newPath);
295
		$fileName = basename($newPath);
296
		$oldFileName = basename($oldPath);
297
298
		list(, , $fileId) = $this->getSourcePathAndOwner($newPath);
299
		list($parentPath, $parentOwner, $parentId) = $this->getSourcePathAndOwner($dirName);
300
		if ($fileId === 0 || $parentId === 0) {
301
			// Could not find the file for the owner ...
302
			return;
303
		}
304
		$affectedUsers = $this->getUserPathsFromPath($parentPath, $parentOwner);
305
306
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'stream', Files::TYPE_SHARE_CHANGED);
307
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'email', Files::TYPE_SHARE_CHANGED);
308
309
		foreach ($affectedUsers as $user => $path) {
310
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
311
				continue;
312
			}
313
314
			if ($user === $this->currentUser->getUID()) {
315
				$userSubject = 'renamed_self';
316
				$userParams = [
317
					[$fileId => $path . '/' . $fileName],
318
					[$fileId => $path . '/' . $oldFileName],
319
				];
320
			} else {
321
				$userSubject = 'renamed_by';
322
				$userParams = [
323
					[$fileId => $path . '/' . $fileName],
324
					$this->currentUser->getUserIdentifier(),
325
					[$fileId => $path . '/' . $oldFileName],
326
				];
327
			}
328
329
			$this->addNotificationsForUser(
330
				$user, $userSubject, $userParams,
331
				$fileId, $path . '/' . $fileName, true,
332
				!empty($filteredStreamUsers[$user]),
333
				!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
334
				Files::TYPE_SHARE_CHANGED
335
			);
336
		}
337
	}
338
339
	/**
340
	 * Moving a file from one folder to another
341
	 *
342
	 * @param string $oldPath
343
	 * @param string $newPath
344
	 */
345
	protected function fileMoving($oldPath, $newPath) {
346
		$dirName = dirname($newPath);
347
		$fileName = basename($newPath);
348
		$oldFileName = basename($oldPath);
349
350
		list(, , $fileId) = $this->getSourcePathAndOwner($newPath);
351
		list($parentPath, $parentOwner, $parentId) = $this->getSourcePathAndOwner($dirName);
352
		if ($fileId === 0 || $parentId === 0) {
353
			// Could not find the file for the owner ...
354
			return;
355
		}
356
		$affectedUsers = $this->getUserPathsFromPath($parentPath, $parentOwner);
357
358
		$beforeUsers = array_keys($this->oldParentUsers);
359
		$afterUsers = array_keys($affectedUsers);
360
361
		$deleteUsers = array_diff($beforeUsers, $afterUsers);
362
		$this->generateDeleteActivities($deleteUsers, $this->oldParentUsers, $fileId, $oldFileName);
363
364
		$addUsers = array_diff($afterUsers, $beforeUsers);
365
		$this->generateAddActivities($addUsers, $affectedUsers, $fileId, $fileName);
366
367
		$moveUsers = array_intersect($beforeUsers, $afterUsers);
368
		$this->generateMoveActivities($moveUsers, $this->oldParentUsers, $affectedUsers, $fileId, $oldFileName, $parentId, $fileName);
369
	}
370
371
	/**
372
	 * @param string[] $users
373
	 * @param string[] $pathMap
374
	 * @param int $fileId
375
	 * @param string $oldFileName
376
	 */
377 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...
378
		if (empty($users)) {
379
			return;
380
		}
381
382
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting($users, 'stream', Files::TYPE_SHARE_DELETED);
383
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', Files::TYPE_SHARE_DELETED);
384
385
		foreach ($users as $user) {
386
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
387
				continue;
388
			}
389
390
			$path = $pathMap[$user];
391
392
			if ($user === $this->currentUser->getUID()) {
393
				$userSubject = 'deleted_self';
394
				$userParams = [[$fileId => $path . '/' . $oldFileName]];
395
			} else {
396
				$userSubject = 'deleted_by';
397
				$userParams = [[$fileId => $path . '/' . $oldFileName], $this->currentUser->getUserIdentifier()];
398
			}
399
400
			$this->addNotificationsForUser(
401
				$user, $userSubject, $userParams,
402
				$fileId, $path . '/' . $oldFileName, true,
403
				!empty($filteredStreamUsers[$user]),
404
				!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
405
				Files::TYPE_SHARE_DELETED
406
			);
407
		}
408
	}
409
410
	/**
411
	 * @param string[] $users
412
	 * @param string[] $pathMap
413
	 * @param int $fileId
414
	 * @param string $fileName
415
	 */
416 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...
417
		if (empty($users)) {
418
			return;
419
		}
420
421
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting($users, 'stream', Files::TYPE_SHARE_CREATED);
422
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', Files::TYPE_SHARE_CREATED);
423
424
		foreach ($users as $user) {
425
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
426
				continue;
427
			}
428
429
			$path = $pathMap[$user];
430
431
			if ($user === $this->currentUser->getUID()) {
432
				$userSubject = 'created_self';
433
				$userParams = [[$fileId => $path . '/' . $fileName]];
434
			} else {
435
				$userSubject = 'created_by';
436
				$userParams = [[$fileId => $path . '/' . $fileName], $this->currentUser->getUserIdentifier()];
437
			}
438
439
			$this->addNotificationsForUser(
440
				$user, $userSubject, $userParams,
441
				$fileId, $path . '/' . $fileName, true,
442
				!empty($filteredStreamUsers[$user]),
443
				!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
444
				Files::TYPE_SHARE_CREATED
445
			);
446
		}
447
	}
448
449
	/**
450
	 * @param string[] $users
451
	 * @param string[] $beforePathMap
452
	 * @param string[] $afterPathMap
453
	 * @param int $fileId
454
	 * @param string $oldFileName
455
	 * @param int $newParentId
456
	 * @param string $fileName
457
	 */
458
	protected function generateMoveActivities($users, $beforePathMap, $afterPathMap, $fileId, $oldFileName, $newParentId, $fileName) {
459
		if (empty($users)) {
460
			return;
461
		}
462
463
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting($users, 'stream', Files::TYPE_SHARE_CHANGED);
464
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', Files::TYPE_SHARE_CHANGED);
465
466
		foreach ($users as $user) {
467
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
468
				continue;
469
			}
470
471
			if ($oldFileName === $fileName) {
472
				$userParams = [[$newParentId => $afterPathMap[$user] . '/']];
473
			} else {
474
				$userParams = [[$fileId => $afterPathMap[$user] . '/' . $fileName]];
475
			}
476
477
			if ($user === $this->currentUser->getUID()) {
478
				$userSubject = 'moved_self';
479
			} else {
480
				$userSubject = 'moved_by';
481
				$userParams[] = $this->currentUser->getUserIdentifier();
482
			}
483
			$userParams[] = [$fileId => $beforePathMap[$user] . '/' . $oldFileName];
484
485
			$this->addNotificationsForUser(
486
				$user, $userSubject, $userParams,
487
				$fileId, $afterPathMap[$user] . '/' . $fileName, true,
488
				!empty($filteredStreamUsers[$user]),
489
				!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
490
				Files::TYPE_SHARE_CHANGED
491
			);
492
		}
493
	}
494
495
	/**
496
	 * Returns a "username => path" map for all affected users
497
	 *
498
	 * @param string $path
499
	 * @param string $uidOwner
500
	 * @return array
501
	 */
502
	protected function getUserPathsFromPath($path, $uidOwner) {
503
		return Share::getUsersSharingFile($path, $uidOwner, true, true);
504
	}
505
506
	/**
507
	 * Return the source
508
	 *
509
	 * @param string $path
510
	 * @return array
511
	 */
512
	protected function getSourcePathAndOwner($path) {
513
		$view = Filesystem::getView();
514
		$uidOwner = $view->getOwner($path);
515
		$fileId = 0;
516
517
		if ($uidOwner !== $this->currentUser->getUID()) {
518
			/** @var \OCP\Files\Storage\IStorage $storage */
519
			list($storage,) = $view->resolvePath($path);
520
			if (!$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
521
				Filesystem::initMountPoints($uidOwner);
522
			} else {
523
				// Probably a remote user, let's try to at least generate activities
524
				// for the current user
525
				$uidOwner = $this->currentUser->getUID();
526
			}
527
		}
528
529
		$info = Filesystem::getFileInfo($path);
530
		if ($info !== false) {
531
			$ownerView = new View('/' . $uidOwner . '/files');
532
			$fileId = (int) $info['fileid'];
533
			$path = $ownerView->getPath($fileId);
534
		}
535
536
		return array($path, $uidOwner, $fileId);
537
	}
538
539
	/**
540
	 * Manage sharing events
541
	 * @param array $params The hook params
542
	 */
543 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...
544 3
		if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
545 3
			if ((int) $params['shareType'] === Share::SHARE_TYPE_USER) {
546 1
				$this->shareFileOrFolderWithUser($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], true);
547 2
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_GROUP) {
548 1
				$this->shareFileOrFolderWithGroup($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], (int) $params['id'], true);
549 1
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_LINK) {
550 1
				$this->shareFileOrFolderByLink((int) $params['fileSource'], $params['itemType'], $params['uidOwner'], true);
551
			}
552
		}
553 3
	}
554
555
	/**
556
	 * Manage sharing events
557
	 * @param array $params The hook params
558
	 */
559 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...
560
		if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
561
			if ((int) $params['shareType'] === Share::SHARE_TYPE_USER) {
562
				$this->shareFileOrFolderWithUser($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], false);
563
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_GROUP) {
564
				$this->shareFileOrFolderWithGroup($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], (int) $params['id'], false);
565
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_LINK) {
566
				$this->shareFileOrFolderByLink((int) $params['fileSource'], $params['itemType'], $params['uidOwner'], false);
567
			}
568
		}
569
	}
570
571
	/**
572
	 * Sharing a file or folder with a user
573
	 *
574
	 * @param string $shareWith
575
	 * @param int $fileSource File ID that is being shared
576
	 * @param string $itemType File type that is being shared (file or folder)
577
	 * @param string $fileTarget File path
578
	 * @param bool $isSharing True if sharing, false if unsharing
579
	 */
580 2
	protected function shareFileOrFolderWithUser($shareWith, $fileSource, $itemType, $fileTarget, $isSharing) {
581 2
		if ($isSharing) {
582 2
			$actionSharer = 'shared_user_self';
583 2
			$actionOwner = 'reshared_user_by';
584 2
			$actionUser = 'shared_with_by';
585
		} else {
586
			$actionSharer = 'unshared_user_self';
587
			$actionOwner = 'unshared_user_by';
588
			$actionUser = 'unshared_by';
589
		}
590
591
		// User performing the share
592 2
		$this->shareNotificationForSharer($actionSharer, $shareWith, $fileSource, $itemType);
593 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...
594 2
			$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), $actionOwner, $shareWith, $fileSource, $itemType);
595
		}
596
597
		// New shared user
598 2
		$this->addNotificationsForUser(
599 2
			$shareWith, $actionUser, [[$fileSource => $fileTarget], $this->currentUser->getUserIdentifier()],
600 2
			(int) $fileSource, $fileTarget, ($itemType === 'file'),
601 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...
602 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...
603
		);
604 2
	}
605
606
	/**
607
	 * Sharing a file or folder with a group
608
	 *
609
	 * @param string $shareWith
610
	 * @param int $fileSource File ID that is being shared
611
	 * @param string $itemType File type that is being shared (file or folder)
612
	 * @param string $fileTarget File path
613
	 * @param int $shareId The Share ID of this share
614
	 * @param bool $isSharing True if sharing, false if unsharing
615
	 */
616 6
	protected function shareFileOrFolderWithGroup($shareWith, $fileSource, $itemType, $fileTarget, $shareId, $isSharing) {
617 6
		if ($isSharing) {
618 6
			$actionSharer = 'shared_group_self';
619 6
			$actionOwner = 'reshared_group_by';
620 6
			$actionUser = 'shared_with_by';
621
		} else {
622
			$actionSharer = 'unshared_group_self';
623
			$actionOwner = 'unshared_group_by';
624
			$actionUser = 'unshared_by';
625
		}
626
627
		// Members of the new group
628 6
		$group = $this->groupManager->get($shareWith);
629 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...
630 1
			return;
631
		}
632
633
		// User performing the share
634 5
		$this->shareNotificationForSharer($actionSharer, $shareWith, $fileSource, $itemType);
635 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...
636 5
			$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), $actionOwner, $shareWith, $fileSource, $itemType);
637
		}
638
639 5
		$offset = 0;
640 5
		$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
641 5
		while (!empty($users)) {
642 4
			$this->addNotificationsForGroupUsers($users, $actionUser, $fileSource, $itemType, $fileTarget, $shareId);
643 4
			$offset += self::USER_BATCH_SIZE;
644 4
			$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
645
		}
646 5
	}
647
648
	/**
649
	 * @param IUser[] $usersInGroup
650
	 * @param string $actionUser
651
	 * @param int $fileSource File ID that is being shared
652
	 * @param string $itemType File type that is being shared (file or folder)
653
	 * @param string $fileTarget File path
654
	 * @param int $shareId The Share ID of this share
655
	 */
656 4
	protected function addNotificationsForGroupUsers(array $usersInGroup, $actionUser, $fileSource, $itemType, $fileTarget, $shareId) {
657 4
		$affectedUsers = [];
658
659 4
		foreach ($usersInGroup as $user) {
660 4
			$affectedUsers[$user->getUID()] = $fileTarget;
661
		}
662
663
		// Remove the triggering user, we already managed his notifications
664 4
		unset($affectedUsers[$this->currentUser->getUID()]);
665
666 4
		if (empty($affectedUsers)) {
667 1
			return;
668
		}
669
670 3
		$userIds = array_keys($affectedUsers);
671 3
		$filteredStreamUsersInGroup = $this->userSettings->filterUsersBySetting($userIds, 'stream', Files_Sharing::TYPE_SHARED);
672 3
		$filteredEmailUsersInGroup = $this->userSettings->filterUsersBySetting($userIds, 'email', Files_Sharing::TYPE_SHARED);
673
674 3
		$affectedUsers = $this->fixPathsForShareExceptions($affectedUsers, $shareId);
675 3
		foreach ($affectedUsers as $user => $path) {
676 3
			if (empty($filteredStreamUsersInGroup[$user]) && empty($filteredEmailUsersInGroup[$user])) {
677 2
				continue;
678
			}
679
680 1
			$this->addNotificationsForUser(
681 1
				$user, $actionUser, [[$fileSource => $path], $this->currentUser->getUserIdentifier()],
682 1
				$fileSource, $path, ($itemType === 'file'),
683 1
				!empty($filteredStreamUsersInGroup[$user]),
684 1
				!empty($filteredEmailUsersInGroup[$user]) ? $filteredEmailUsersInGroup[$user] : 0
685
			);
686
		}
687 3
	}
688
689
	/**
690
	 * Check when there was a naming conflict and the target is different
691
	 * for some of the users
692
	 *
693
	 * @param array $affectedUsers
694
	 * @param int $shareId
695
	 * @return mixed
696
	 */
697
	protected function fixPathsForShareExceptions(array $affectedUsers, $shareId) {
698
		$queryBuilder = $this->connection->getQueryBuilder();
699
		$queryBuilder->select(['share_with', 'file_target'])
700
			->from('share')
701
			->where($queryBuilder->expr()->eq('parent', $queryBuilder->createParameter('parent')))
702
			->setParameter('parent', (int) $shareId);
703
		$query = $queryBuilder->execute();
704
705
		while ($row = $query->fetch()) {
706
			$affectedUsers[$row['share_with']] = $row['file_target'];
707
		}
708
709
		return $affectedUsers;
710
	}
711
712
	/**
713
	 * Sharing a file or folder via link/public
714
	 *
715
	 * @param int $fileSource File ID that is being shared
716
	 * @param string $itemType File type that is being shared (file or folder)
717
	 * @param string $linkOwner
718
	 * @param bool $isSharing True if sharing, false if unsharing
719
	 */
720 2
	protected function shareFileOrFolderByLink($fileSource, $itemType, $linkOwner, $isSharing) {
721 2
		$owner = $this->currentUser->getUID();
722 2
		if ($isSharing) {
723 2
			$actionSharer = 'shared_link_self';
724 2
			$actionOwner = 'reshared_link_by';
725
		} else if ($owner !== $linkOwner) {
726
			// Link expired
727
			$actionSharer = 'link_expired';
728
			$actionOwner = 'link_by_expired';
729
			$owner = $linkOwner;
730
			\OC::$server->getUserFolder($linkOwner);
731
		} else {
732
			$actionSharer = 'unshared_link_self';
733
			$actionOwner = 'unshared_link_by';
734
		}
735
736 2
		$this->view->chroot('/' . $owner . '/files');
737
738
		try {
739 2
			$path = $this->view->getPath($fileSource);
740 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...
741 1
			return;
742
		}
743
744 1
		$this->shareNotificationForOriginalOwners($owner, $actionOwner, '', $fileSource, $itemType);
745
746 1
		$this->addNotificationsForUser(
747 1
			$owner, $actionSharer, [[$fileSource => $path]],
748 1
			(int) $fileSource, $path, ($itemType === 'file'),
749 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...
750 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...
751
		);
752 1
	}
753
754
	/**
755
	 * Add notifications for the user that shares a file/folder
756
	 *
757
	 * @param string $subject
758
	 * @param string $shareWith
759
	 * @param int $fileSource
760
	 * @param string $itemType
761
	 */
762 2
	protected function shareNotificationForSharer($subject, $shareWith, $fileSource, $itemType) {
763 2
		$sharer = $this->currentUser->getUID();
764 2
		if ($sharer === null) {
765
			return;
766
		}
767
768 2
		$this->view->chroot('/' . $sharer . '/files');
769
770
		try {
771 2
			$path = $this->view->getPath($fileSource);
772 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...
773 1
			return;
774
		}
775
776 1
		$this->addNotificationsForUser(
777 1
			$sharer, $subject, [[$fileSource => $path], $shareWith],
778 1
			$fileSource, $path, ($itemType === 'file'),
779 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...
780 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...
781
		);
782 1
	}
783
784
	/**
785
	 * Add notifications for the user that shares a file/folder
786
	 *
787
	 * @param string $owner
788
	 * @param string $subject
789
	 * @param string $shareWith
790
	 * @param int $fileSource
791
	 * @param string $itemType
792
	 */
793 2
	protected function reshareNotificationForSharer($owner, $subject, $shareWith, $fileSource, $itemType) {
794 2
		$this->view->chroot('/' . $owner . '/files');
795
796
		try {
797 2
			$path = $this->view->getPath($fileSource);
798 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...
799 1
			return;
800
		}
801
802 1
		$this->addNotificationsForUser(
803 1
			$owner, $subject, [[$fileSource => $path], $this->currentUser->getUserIdentifier(), $shareWith],
804 1
			$fileSource, $path, ($itemType === 'file'),
805 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...
806 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...
807
		);
808 1
	}
809
810
	/**
811
	 * Add notifications for the owners whose files have been reshared
812
	 *
813
	 * @param string $currentOwner
814
	 * @param string $subject
815
	 * @param string $shareWith
816
	 * @param int $fileSource
817
	 * @param string $itemType
818
	 */
819 10
	protected function shareNotificationForOriginalOwners($currentOwner, $subject, $shareWith, $fileSource, $itemType) {
820
		// Get the full path of the current user
821 10
		$this->view->chroot('/' . $currentOwner . '/files');
822
823
		try {
824 10
			$path = $this->view->getPath($fileSource);
825 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...
826 1
			return;
827
		}
828
829
		/**
830
		 * Get the original owner and his path
831
		 */
832 9
		$owner = $this->view->getOwner($path);
833 9
		if ($owner !== $currentOwner) {
834 7
			$this->reshareNotificationForSharer($owner, $subject, $shareWith, $fileSource, $itemType);
835
		}
836
837
		/**
838
		 * Get the sharee who shared the item with the currentUser
839
		 */
840 9
		$this->view->chroot('/' . $currentOwner . '/files');
841 9
		$mount = $this->view->getMount($path);
842 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...
843 1
			return;
844
		}
845
846 8
		$storage = $mount->getStorage();
847 8
		if (!$storage->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) {
848 1
			return;
849
		}
850
851
		/** @var \OCA\Files_Sharing\SharedStorage $storage */
852 7
		$shareOwner = $storage->getSharedFrom();
853 7
		if ($shareOwner === '' || $shareOwner === null || $shareOwner === $owner || $shareOwner === $currentOwner) {
854 5
			return;
855
		}
856
857 2
		$this->reshareNotificationForSharer($shareOwner, $subject, $shareWith, $fileSource, $itemType);
858 2
	}
859
860
	/**
861
	 * Adds the activity and email for a user when the settings require it
862
	 *
863
	 * @param string $user
864
	 * @param string $subject
865
	 * @param array $subjectParams
866
	 * @param int $fileId
867
	 * @param string $path
868
	 * @param bool $isFile If the item is a file, we link to the parent directory
869
	 * @param bool $streamSetting
870
	 * @param int $emailSetting
871
	 * @param string $type
872
	 */
873 11
	protected function addNotificationsForUser($user, $subject, $subjectParams, $fileId, $path, $isFile, $streamSetting, $emailSetting, $type = Files_Sharing::TYPE_SHARED) {
874 11
		if (!$streamSetting && !$emailSetting) {
875 1
			return;
876
		}
877
878 10
		$selfAction = $user === $this->currentUser->getUID();
879 10
		$app = $type === Files_Sharing::TYPE_SHARED ? 'files_sharing' : 'files';
880 10
		$link = $this->urlGenerator->linkToRouteAbsolute('files.view.index', array(
881 10
			'dir' => ($isFile) ? dirname($path) : $path,
882
		));
883
884 10
		$objectType = ($fileId) ? 'files' : '';
885
886 10
		$event = $this->manager->generateEvent();
887 10
		$event->setApp($app)
888 10
			->setType($type)
889 10
			->setAffectedUser($user)
890 10
			->setAuthor($this->currentUser->getUID())
891 10
			->setTimestamp(time())
892 10
			->setSubject($subject, $subjectParams)
893 10
			->setObject($objectType, $fileId, $path)
894 10
			->setLink($link);
895
896
		// Add activity to stream
897 10
		if ($streamSetting && (!$selfAction || $this->userSettings->getUserSetting($this->currentUser->getUID(), 'setting', 'self'))) {
898 3
			$this->activityData->send($event);
899
		}
900
901
		// Add activity to mail queue
902 10
		if ($emailSetting && (!$selfAction || $this->userSettings->getUserSetting($this->currentUser->getUID(), 'setting', 'selfemail'))) {
903 5
			$latestSend = time() + $emailSetting;
904 5
			$this->activityData->storeMail($event, $latestSend);
905
		}
906 10
	}
907
}
908