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