Completed
Pull Request — master (#120)
by Morris
08:46
created

FilesHooks::shareFileOrFolderWithUser()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 25
Code Lines 17

Duplication

Lines 3
Ratio 12 %

Code Coverage

Tests 17
CRAP Score 4.054

Importance

Changes 0
Metric Value
dl 3
loc 25
ccs 17
cts 20
cp 0.85
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 17
nc 4
nop 5
crap 4.054
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 1
		} 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 1
			} else {
181 1
				$userSubject = $subjectBy;
182 1
				$userParams = [[$fileId => $path], $this->currentUser->getUserIdentifier()];
183
			}
184
185 2
			$this->addNotificationsForUser(
186 2
				$user, $userSubject, $userParams,
187 2
				$fileId, $path, true,
188 2
				!empty($filteredStreamUsers[$user]),
189 2
				!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
190
				$activityType
191 2
			);
192 2
		}
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
		$owner = $view->getOwner($path);
521
		$owner = !is_string($owner) || $owner === '' ? null : $owner;
522
		$fileId = 0;
523
		$currentUser = $this->currentUser->getUID();
524
525
		if ($owner === null || $owner !== $currentUser) {
526
			/** @var \OCP\Files\Storage\IStorage $storage */
527
			list($storage,) = $view->resolvePath($path);
528
529
			if ($owner !== null && !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
530
				Filesystem::initMountPoints($owner);
531
			} else {
532
				// Probably a remote user, let's try to at least generate activities
533
				// for the current user
534
				if ($currentUser === null) {
535
					list(,$owner,) = explode('/', $view->getAbsolutePath($path), 3);
536
				} else {
537
					$owner = $currentUser;
538
				}
539
			}
540
		}
541
542
		$info = Filesystem::getFileInfo($path);
543
		if ($info !== false) {
544
			$ownerView = new View('/' . $owner . '/files');
545
			$fileId = (int) $info['fileid'];
546
			$path = $ownerView->getPath($fileId);
547
		}
548
549
		return array($path, $owner, $fileId);
550
	}
551
552
	/**
553
	 * Manage sharing events
554
	 * @param array $params The hook params
555
	 */
556 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...
557 3
		if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
558 3
			if ((int) $params['shareType'] === Share::SHARE_TYPE_USER) {
559 1
				$this->shareFileOrFolderWithUser($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], true);
560 3
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_GROUP) {
561 1
				$this->shareFileOrFolderWithGroup($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], (int) $params['id'], true);
562 2
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_LINK) {
563 1
				$this->shareFileOrFolderByLink((int) $params['fileSource'], $params['itemType'], $params['uidOwner'], true);
564 1
			}
565 3
		}
566 3
	}
567
568
	/**
569
	 * Manage sharing events
570
	 * @param array $params The hook params
571
	 */
572 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...
573
		if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
574
			if ((int) $params['shareType'] === Share::SHARE_TYPE_USER) {
575
				$this->shareFileOrFolderWithUser($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], false);
576
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_GROUP) {
577
				$this->shareFileOrFolderWithGroup($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], (int) $params['id'], false);
578
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_LINK) {
579
				$this->shareFileOrFolderByLink((int) $params['fileSource'], $params['itemType'], $params['uidOwner'], false);
580
			}
581
		}
582
	}
583
584
	/**
585
	 * Sharing a file or folder with a user
586
	 *
587
	 * @param string $shareWith
588
	 * @param int $fileSource File ID that is being shared
589
	 * @param string $itemType File type that is being shared (file or folder)
590
	 * @param string $fileTarget File path
591
	 * @param bool $isSharing True if sharing, false if unsharing
592
	 */
593 2
	protected function shareFileOrFolderWithUser($shareWith, $fileSource, $itemType, $fileTarget, $isSharing) {
594 2
		if ($isSharing) {
595 2
			$actionSharer = 'shared_user_self';
596 2
			$actionOwner = 'reshared_user_by';
597 2
			$actionUser = 'shared_with_by';
598 2
		} else {
599
			$actionSharer = 'unshared_user_self';
600
			$actionOwner = 'unshared_user_by';
601
			$actionUser = 'unshared_by';
602
		}
603
604
		// User performing the share
605 2
		$this->shareNotificationForSharer($actionSharer, $shareWith, $fileSource, $itemType);
606 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...
607 2
			$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), $actionOwner, $shareWith, $fileSource, $itemType);
608 2
		}
609
610
		// New shared user
611 2
		$this->addNotificationsForUser(
612 2
			$shareWith, $actionUser, [[$fileSource => $fileTarget], $this->currentUser->getUserIdentifier()],
613 2
			(int) $fileSource, $fileTarget, ($itemType === 'file'),
614 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...
615 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...
616 2
		);
617 2
	}
618
619
	/**
620
	 * Sharing a file or folder with a group
621
	 *
622
	 * @param string $shareWith
623
	 * @param int $fileSource File ID that is being shared
624
	 * @param string $itemType File type that is being shared (file or folder)
625
	 * @param string $fileTarget File path
626
	 * @param int $shareId The Share ID of this share
627
	 * @param bool $isSharing True if sharing, false if unsharing
628
	 */
629 6
	protected function shareFileOrFolderWithGroup($shareWith, $fileSource, $itemType, $fileTarget, $shareId, $isSharing) {
630 6
		if ($isSharing) {
631 6
			$actionSharer = 'shared_group_self';
632 6
			$actionOwner = 'reshared_group_by';
633 6
			$actionUser = 'shared_with_by';
634 6
		} else {
635
			$actionSharer = 'unshared_group_self';
636
			$actionOwner = 'unshared_group_by';
637
			$actionUser = 'unshared_by';
638
		}
639
640
		// Members of the new group
641 6
		$group = $this->groupManager->get($shareWith);
642 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...
643 1
			return;
644
		}
645
646
		// User performing the share
647 5
		$this->shareNotificationForSharer($actionSharer, $shareWith, $fileSource, $itemType);
648 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...
649 5
			$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), $actionOwner, $shareWith, $fileSource, $itemType);
650 5
		}
651
652 5
		$offset = 0;
653 5
		$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
654 5
		while (!empty($users)) {
655 4
			$this->addNotificationsForGroupUsers($users, $actionUser, $fileSource, $itemType, $fileTarget, $shareId);
656 4
			$offset += self::USER_BATCH_SIZE;
657 4
			$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
658 4
		}
659 5
	}
660
661
	/**
662
	 * @param IUser[] $usersInGroup
663
	 * @param string $actionUser
664
	 * @param int $fileSource File ID that is being shared
665
	 * @param string $itemType File type that is being shared (file or folder)
666
	 * @param string $fileTarget File path
667
	 * @param int $shareId The Share ID of this share
668
	 */
669 4
	protected function addNotificationsForGroupUsers(array $usersInGroup, $actionUser, $fileSource, $itemType, $fileTarget, $shareId) {
670 4
		$affectedUsers = [];
671
672 4
		foreach ($usersInGroup as $user) {
673 4
			$affectedUsers[$user->getUID()] = $fileTarget;
674 4
		}
675
676
		// Remove the triggering user, we already managed his notifications
677 4
		unset($affectedUsers[$this->currentUser->getUID()]);
678
679 4
		if (empty($affectedUsers)) {
680 1
			return;
681
		}
682
683 3
		$userIds = array_keys($affectedUsers);
684 3
		$filteredStreamUsersInGroup = $this->userSettings->filterUsersBySetting($userIds, 'stream', Files_Sharing::TYPE_SHARED);
685 3
		$filteredEmailUsersInGroup = $this->userSettings->filterUsersBySetting($userIds, 'email', Files_Sharing::TYPE_SHARED);
686
687 3
		$affectedUsers = $this->fixPathsForShareExceptions($affectedUsers, $shareId);
688 3
		foreach ($affectedUsers as $user => $path) {
689 3
			if (empty($filteredStreamUsersInGroup[$user]) && empty($filteredEmailUsersInGroup[$user])) {
690 2
				continue;
691
			}
692
693 1
			$this->addNotificationsForUser(
694 1
				$user, $actionUser, [[$fileSource => $path], $this->currentUser->getUserIdentifier()],
695 1
				$fileSource, $path, ($itemType === 'file'),
696 1
				!empty($filteredStreamUsersInGroup[$user]),
697 1
				!empty($filteredEmailUsersInGroup[$user]) ? $filteredEmailUsersInGroup[$user] : 0
698 1
			);
699 3
		}
700 3
	}
701
702
	/**
703
	 * Check when there was a naming conflict and the target is different
704
	 * for some of the users
705
	 *
706
	 * @param array $affectedUsers
707
	 * @param int $shareId
708
	 * @return mixed
709
	 */
710
	protected function fixPathsForShareExceptions(array $affectedUsers, $shareId) {
711
		$queryBuilder = $this->connection->getQueryBuilder();
712
		$queryBuilder->select(['share_with', 'file_target'])
713
			->from('share')
714
			->where($queryBuilder->expr()->eq('parent', $queryBuilder->createParameter('parent')))
715
			->setParameter('parent', (int) $shareId);
716
		$query = $queryBuilder->execute();
717
718
		while ($row = $query->fetch()) {
719
			$affectedUsers[$row['share_with']] = $row['file_target'];
720
		}
721
722
		return $affectedUsers;
723
	}
724
725
	/**
726
	 * Sharing a file or folder via link/public
727
	 *
728
	 * @param int $fileSource File ID that is being shared
729
	 * @param string $itemType File type that is being shared (file or folder)
730
	 * @param string $linkOwner
731
	 * @param bool $isSharing True if sharing, false if unsharing
732
	 */
733 2
	protected function shareFileOrFolderByLink($fileSource, $itemType, $linkOwner, $isSharing) {
734 2
		$owner = $this->currentUser->getUID();
735 2
		if ($isSharing) {
736 2
			$actionSharer = 'shared_link_self';
737 2
			$actionOwner = 'reshared_link_by';
738 2
		} else if ($owner !== $linkOwner) {
739
			// Link expired
740
			$actionSharer = 'link_expired';
741
			$actionOwner = 'link_by_expired';
742
			$owner = $linkOwner;
743
			\OC::$server->getUserFolder($linkOwner);
744
		} else {
745
			$actionSharer = 'unshared_link_self';
746
			$actionOwner = 'unshared_link_by';
747
		}
748
749 2
		$this->view->chroot('/' . $owner . '/files');
750
751
		try {
752 2
			$path = $this->view->getPath($fileSource);
753 2
		} 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...
754 1
			return;
755
		}
756
757 1
		$this->shareNotificationForOriginalOwners($owner, $actionOwner, '', $fileSource, $itemType);
758
759 1
		$this->addNotificationsForUser(
760 1
			$owner, $actionSharer, [[$fileSource => $path]],
761 1
			(int) $fileSource, $path, ($itemType === 'file'),
762 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...
763 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...
764 1
		);
765 1
	}
766
767
	/**
768
	 * Add notifications for the user that shares a file/folder
769
	 *
770
	 * @param string $subject
771
	 * @param string $shareWith
772
	 * @param int $fileSource
773
	 * @param string $itemType
774
	 */
775 2
	protected function shareNotificationForSharer($subject, $shareWith, $fileSource, $itemType) {
776 2
		$sharer = $this->currentUser->getUID();
777 2
		if ($sharer === null) {
778
			return;
779
		}
780
781 2
		$this->view->chroot('/' . $sharer . '/files');
782
783
		try {
784 2
			$path = $this->view->getPath($fileSource);
785 2
		} 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...
786 1
			return;
787
		}
788
789 1
		$this->addNotificationsForUser(
790 1
			$sharer, $subject, [[$fileSource => $path], $shareWith],
791 1
			$fileSource, $path, ($itemType === 'file'),
792 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...
793 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...
794 1
		);
795 1
	}
796
797
	/**
798
	 * Add notifications for the user that shares a file/folder
799
	 *
800
	 * @param string $owner
801
	 * @param string $subject
802
	 * @param string $shareWith
803
	 * @param int $fileSource
804
	 * @param string $itemType
805
	 */
806 2
	protected function reshareNotificationForSharer($owner, $subject, $shareWith, $fileSource, $itemType) {
807 2
		$this->view->chroot('/' . $owner . '/files');
808
809
		try {
810 2
			$path = $this->view->getPath($fileSource);
811 2
		} 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...
812 1
			return;
813
		}
814
815 1
		$this->addNotificationsForUser(
816 1
			$owner, $subject, [[$fileSource => $path], $this->currentUser->getUserIdentifier(), $shareWith],
817 1
			$fileSource, $path, ($itemType === 'file'),
818 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...
819 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...
820 1
		);
821 1
	}
822
823
	/**
824
	 * Add notifications for the owners whose files have been reshared
825
	 *
826
	 * @param string $currentOwner
827
	 * @param string $subject
828
	 * @param string $shareWith
829
	 * @param int $fileSource
830
	 * @param string $itemType
831
	 */
832 10
	protected function shareNotificationForOriginalOwners($currentOwner, $subject, $shareWith, $fileSource, $itemType) {
833
		// Get the full path of the current user
834 10
		$this->view->chroot('/' . $currentOwner . '/files');
835
836
		try {
837 10
			$path = $this->view->getPath($fileSource);
838 10
		} 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...
839 1
			return;
840
		}
841
842
		/**
843
		 * Get the original owner and his path
844
		 */
845 9
		$owner = $this->view->getOwner($path);
846 9
		if ($owner !== $currentOwner) {
847 7
			$this->reshareNotificationForSharer($owner, $subject, $shareWith, $fileSource, $itemType);
848 7
		}
849
850
		/**
851
		 * Get the sharee who shared the item with the currentUser
852
		 */
853 9
		$this->view->chroot('/' . $currentOwner . '/files');
854 9
		$mount = $this->view->getMount($path);
855 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...
856 1
			return;
857
		}
858
859 8
		$storage = $mount->getStorage();
860 8
		if (!$storage->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) {
861 1
			return;
862
		}
863
864
		/** @var \OCA\Files_Sharing\SharedStorage $storage */
865 7
		$shareOwner = $storage->getSharedFrom();
866 7
		if ($shareOwner === '' || $shareOwner === null || $shareOwner === $owner || $shareOwner === $currentOwner) {
867 5
			return;
868
		}
869
870 2
		$this->reshareNotificationForSharer($shareOwner, $subject, $shareWith, $fileSource, $itemType);
871 2
	}
872
873
	/**
874
	 * Adds the activity and email for a user when the settings require it
875
	 *
876
	 * @param string $user
877
	 * @param string $subject
878
	 * @param array $subjectParams
879
	 * @param int $fileId
880
	 * @param string $path
881
	 * @param bool $isFile If the item is a file, we link to the parent directory
882
	 * @param bool $streamSetting
883
	 * @param int $emailSetting
884
	 * @param string $type
885
	 */
886 11
	protected function addNotificationsForUser($user, $subject, $subjectParams, $fileId, $path, $isFile, $streamSetting, $emailSetting, $type = Files_Sharing::TYPE_SHARED) {
887 11
		if (!$streamSetting && !$emailSetting) {
888 1
			return;
889
		}
890
891 10
		$selfAction = $user === $this->currentUser->getUID();
892 10
		$app = $type === Files_Sharing::TYPE_SHARED ? 'files_sharing' : 'files';
893 10
		$link = $this->urlGenerator->linkToRouteAbsolute('files.view.index', array(
894 10
			'dir' => ($isFile) ? dirname($path) : $path,
895 10
		));
896
897 10
		$objectType = ($fileId) ? 'files' : '';
898
899 10
		$event = $this->manager->generateEvent();
900
		try {
901 10
			$event->setApp($app)
902 10
				->setType($type)
903 10
				->setAffectedUser($user)
904 10
				->setTimestamp(time())
905 10
				->setSubject($subject, $subjectParams)
906 10
				->setObject($objectType, $fileId, $path)
907 10
				->setLink($link);
908
909 10
			if ($this->currentUser->getUID() !== null) {
910
				// Allow this to be empty for guests
911 10
				$event->setAuthor($this->currentUser->getUID());
912 10
			}
913 10
		} catch (\InvalidArgumentException $e) {
914
			$this->logger->logException($e);
915
		}
916
917
		// Add activity to stream
918 10
		if ($streamSetting && (!$selfAction || $this->userSettings->getUserSetting($this->currentUser->getUID(), 'setting', 'self'))) {
919 3
			$this->activityData->send($event);
920 3
		}
921
922
		// Add activity to mail queue
923 10
		if ($emailSetting && (!$selfAction || $this->userSettings->getUserSetting($this->currentUser->getUID(), 'setting', 'selfemail'))) {
924 5
			$latestSend = time() + $emailSetting;
925 5
			$this->activityData->storeMail($event, $latestSend);
926 5
		}
927 10
	}
928
}
929