Issues (159)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/FilesHooks.php (30 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\BackgroundJob\RemoteActivity;
30
use OCA\Activity\Extension\Files;
31
use OCA\Activity\Extension\Files_Sharing;
32
use OCP\Activity\IManager;
33
use OCP\Files\IRootFolder;
34
use OCP\Files\Mount\IMountPoint;
35
use OCP\Files\Node;
36
use OCP\Files\NotFoundException;
37
use OCP\IDBConnection;
38
use OCP\IGroup;
39
use OCP\IGroupManager;
40
use OCP\ILogger;
41
use OCP\IURLGenerator;
42
use OCP\IUser;
43
use OCP\Share;
44
use OCP\Share\IShare;
45
use OCP\Share\IShareHelper;
46
47
/**
48
 * The class to handle the filesystem hooks
49
 */
50
class FilesHooks {
51
	const USER_BATCH_SIZE = 50;
52
53
	/** @var \OCP\Activity\IManager */
54
	protected $manager;
55
56
	/** @var \OCA\Activity\Data */
57
	protected $activityData;
58
59
	/** @var \OCA\Activity\UserSettings */
60
	protected $userSettings;
61
62
	/** @var \OCP\IGroupManager */
63
	protected $groupManager;
64
65
	/** @var \OCP\IDBConnection */
66
	protected $connection;
67
68
	/** @var \OC\Files\View */
69
	protected $view;
70
71
	/** @var IRootFolder */
72
	protected $rootFolder;
73
74
	/** @var IShareHelper */
75
	protected $shareHelper;
76
77
	/** @var IURLGenerator */
78
	protected $urlGenerator;
79
80
	/** @var ILogger */
81
	protected $logger;
82
83
	/** @var CurrentUser */
84
	protected $currentUser;
85
86
	/** @var string|bool */
87
	protected $moveCase = false;
88
	/** @var array */
89
	protected $oldAccessList;
90
	/** @var string */
91
	protected $oldParentPath;
92
	/** @var string */
93
	protected $oldParentOwner;
94
	/** @var string */
95
	protected $oldParentId;
96
97
	/**
98
	 * Constructor
99
	 *
100
	 * @param IManager $manager
101
	 * @param Data $activityData
102
	 * @param UserSettings $userSettings
103
	 * @param IGroupManager $groupManager
104
	 * @param View $view
105
	 * @param IRootFolder $rootFolder
106
	 * @param IShareHelper $shareHelper
107
	 * @param IDBConnection $connection
108
	 * @param IURLGenerator $urlGenerator
109
	 * @param ILogger $logger
110
	 * @param CurrentUser $currentUser
111
	 */
112 49
	public function __construct(IManager $manager,
113
								Data $activityData,
114
								UserSettings $userSettings,
115
								IGroupManager $groupManager,
116
								View $view,
117
								IRootFolder $rootFolder,
118
								IShareHelper $shareHelper,
119
								IDBConnection $connection,
120
								IURLGenerator $urlGenerator,
121
								ILogger $logger,
122
								CurrentUser $currentUser) {
123 49
		$this->manager = $manager;
124 49
		$this->activityData = $activityData;
125 49
		$this->userSettings = $userSettings;
126 49
		$this->groupManager = $groupManager;
127 49
		$this->view = $view;
128 49
		$this->rootFolder = $rootFolder;
129 49
		$this->shareHelper = $shareHelper;
130 49
		$this->connection = $connection;
131 49
		$this->urlGenerator = $urlGenerator;
132 49
		$this->logger = $logger;
133 49
		$this->currentUser = $currentUser;
134 49
	}
135
136
	/**
137
	 * Store the create hook events
138
	 * @param string $path Path of the file that has been created
139
	 */
140 4
	public function fileCreate($path) {
141 4
		if ($path === '/' || $path === '' || $path === null) {
142 2
			return;
143
		}
144
145 2
		if ($this->currentUser->getUserIdentifier() !== '') {
146 1
			$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CREATED, 'created_self', 'created_by');
147
		} else {
148 1
			$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CREATED, '', 'created_public');
149
		}
150 2
	}
151
152
	/**
153
	 * Store the update hook events
154
	 * @param string $path Path of the file that has been modified
155
	 */
156 1
	public function fileUpdate($path) {
157 1
		$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CHANGED, 'changed_self', 'changed_by');
158 1
	}
159
160
	/**
161
	 * Store the delete hook events
162
	 * @param string $path Path of the file that has been deleted
163
	 */
164 1
	public function fileDelete($path) {
165 1
		$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_DELETED, 'deleted_self', 'deleted_by');
166 1
	}
167
168
	/**
169
	 * Store the restore hook events
170
	 * @param string $path Path of the file that has been restored
171
	 */
172 1
	public function fileRestore($path) {
173 1
		$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_RESTORED, 'restored_self', 'restored_by');
174 1
	}
175
176
	/**
177
	 * Creates the entries for file actions on $file_path
178
	 *
179
	 * @param string $filePath         The file that is being changed
180
	 * @param int    $activityType     The activity type
181
	 * @param string $subject          The subject for the actor
182
	 * @param string $subjectBy        The subject for other users (with "by $actor")
183
	 */
184 3
	protected function addNotificationsForFileAction($filePath, $activityType, $subject, $subjectBy) {
185
		// Do not add activities for .part-files
186 3
		if (substr($filePath, -5) === '.part') {
187 1
			return;
188
		}
189
190 2
		list($filePath, $uidOwner, $fileId) = $this->getSourcePathAndOwner($filePath);
191 2
		if ($fileId === 0) {
192
			// Could not find the file for the owner ...
193
			return;
194
		}
195
196 2
		$accessList = $this->getUserPathsFromPath($filePath, $uidOwner);
197
198 2
		$this->generateRemoteActivity($accessList['remotes'], $activityType, time(), $this->currentUser->getCloudId(), $accessList['ownerPath']);
199
200 2
		$affectedUsers = $accessList['users'];
201 2
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'stream', $activityType);
202 2
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'email', $activityType);
203
204 2
		foreach ($affectedUsers as $user => $path) {
205 2
			$user = (string) $user;
206 2
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
207 2
				continue;
208
			}
209
210 2
			if ($user === $this->currentUser->getUID()) {
211 1
				$userSubject = $subject;
212 1
				$userParams = [[$fileId => $path]];
213
			} else {
214 1
				$userSubject = $subjectBy;
215 1
				$userParams = [[$fileId => $path], $this->currentUser->getUserIdentifier()];
216
			}
217
218 2
			$this->addNotificationsForUser(
219 2
				$user, $userSubject, $userParams,
220 2
				$fileId, $path, true,
221 2
				!empty($filteredStreamUsers[$user]),
222 2
				$filteredEmailUsers[$user] ?? false,
223
				$activityType
224
			);
225
		}
226 2
	}
227
228 2
	protected function generateRemoteActivity(array $remoteUsers, $type, $time, $actor, $ownerPath = false) {
229 2
		foreach ($remoteUsers as $remoteUser => $info) {
230
			if ($actor === $remoteUser) {
231
				// Current user receives the notification on their own instance already
232
				continue;
233
			}
234
235
			$arguments = [
236
				$remoteUser,
237
				$info['token'],
238
				$ownerPath !== false ? substr($ownerPath, strlen($info['node_path'])) : $info['node_path'],
239
				$type,
240
				$time,
241
				$actor,
242
			];
243
244
			if (isset($info['second_path'])) {
245
				$arguments[] = $info['second_path'];
246
			}
247
248
			\OC::$server->getJobList()->add(RemoteActivity::class, $arguments);
249
		}
250 2
	}
251
252
	/**
253
	 * Collect some information for move/renames
254
	 *
255
	 * @param string $oldPath Path of the file that has been moved
256
	 * @param string $newPath Path of the file that has been moved
257
	 */
258
	public function fileMove($oldPath, $newPath) {
259
		if (substr($oldPath, -5) === '.part' || substr($newPath, -5) === '.part') {
260
			// Do not add activities for .part-files
261
			$this->moveCase = false;
262
			return;
263
		}
264
265
		$oldDir = dirname($oldPath);
266
		$newDir = dirname($newPath);
267
268
		if ($oldDir === $newDir) {
269
			/**
270
			 * a/b moved to a/c
271
			 *
272
			 * Cases:
273
			 * - a/b shared: no visible change
274
			 * - a/ shared: rename
275
			 */
276
			$this->moveCase = 'rename';
277
			return;
278
		}
279
280
		if (strpos($oldDir, $newDir) === 0) {
281
			/**
282
			 * a/b/c moved to a/c
283
			 *
284
			 * Cases:
285
			 * - a/b/c shared: no visible change
286
			 * - a/b/ shared: delete
287
			 * - a/ shared: move/rename
288
			 */
289
			$this->moveCase = 'moveUp';
290
		} else if (strpos($newDir, $oldDir) === 0) {
291
			/**
292
			 * a/b moved to a/c/b
293
			 *
294
			 * Cases:
295
			 * - a/b shared: no visible change
296
			 * - a/c/ shared: add
297
			 * - a/ shared: move/rename
298
			 */
299
			$this->moveCase = 'moveDown';
300
		} else {
301
			/**
302
			 * a/b/c moved to a/d/c
303
			 *
304
			 * Cases:
305
			 * - a/b/c shared: no visible change
306
			 * - a/b/ shared: delete
307
			 * - a/d/ shared: add
308
			 * - a/ shared: move/rename
309
			 */
310
			$this->moveCase = 'moveCross';
311
		}
312
313
		list($this->oldParentPath, $this->oldParentOwner, $this->oldParentId) = $this->getSourcePathAndOwner($oldDir);
314
		if ($this->oldParentId === 0) {
315
			// Could not find the file for the owner ...
316
			$this->moveCase = false;
317
			return;
318
		}
319
		$this->oldAccessList = $this->getUserPathsFromPath($this->oldParentPath, $this->oldParentOwner);
320
	}
321
322
323
	/**
324
	 * Store the move hook events
325
	 *
326
	 * @param string $oldPath Path of the file that has been moved
327
	 * @param string $newPath Path of the file that has been moved
328
	 */
329
	public function fileMovePost($oldPath, $newPath) {
330
		// Do not add activities for .part-files
331
		if ($this->moveCase === false) {
332
			return;
333
		}
334
335
		switch ($this->moveCase) {
336
			case 'rename':
337
				$this->fileRenaming($oldPath, $newPath);
338
				break;
339
			case 'moveUp':
340
			case 'moveDown':
341
			case 'moveCross':
342
				$this->fileMoving($oldPath, $newPath);
343
				break;
344
		}
345
346
		$this->moveCase = false;
347
	}
348
349
350
	/**
351
	 * Renaming a file inside the same folder (a/b to a/c)
352
	 *
353
	 * @param string $oldPath
354
	 * @param string $newPath
355
	 */
356
	protected function fileRenaming($oldPath, $newPath) {
357
		$dirName = dirname($newPath);
358
		$fileName = basename($newPath);
359
		$oldFileName = basename($oldPath);
360
361
		list(, , $fileId) = $this->getSourcePathAndOwner($newPath);
362
		list($parentPath, $parentOwner, $parentId) = $this->getSourcePathAndOwner($dirName);
363
		if ($fileId === 0 || $parentId === 0) {
364
			// Could not find the file for the owner ...
365
			return;
366
		}
367
		$accessList = $this->getUserPathsFromPath($parentPath, $parentOwner);
368
369
		$renameRemotes = [];
370
		foreach ($accessList['remotes'] as $remote => $info) {
371
			$renameRemotes[$remote] = [
372
				'token'       => $info['token'],
373
				'node_path'   => substr($newPath, strlen($info['node_path'])),
374
				'second_path' => substr($oldPath, strlen($info['node_path'])),
375
			];
376
		}
377
		$this->generateRemoteActivity($renameRemotes, Files::TYPE_SHARE_CHANGED, time(), $this->currentUser->getCloudId());
378
379
		$affectedUsers = $accessList['users'];
380
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'stream', Files::TYPE_SHARE_CHANGED);
381
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'email', Files::TYPE_SHARE_CHANGED);
382
383
		foreach ($affectedUsers as $user => $path) {
384
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
385
				continue;
386
			}
387
388
			if ($user === $this->currentUser->getUID()) {
389
				$userSubject = 'renamed_self';
390
				$userParams = [
391
					[$fileId => $path . '/' . $fileName],
392
					[$fileId => $path . '/' . $oldFileName],
393
				];
394
			} else {
395
				$userSubject = 'renamed_by';
396
				$userParams = [
397
					[$fileId => $path . '/' . $fileName],
398
					$this->currentUser->getUserIdentifier(),
399
					[$fileId => $path . '/' . $oldFileName],
400
				];
401
			}
402
403
			$this->addNotificationsForUser(
404
				$user, $userSubject, $userParams,
405
				$fileId, $path . '/' . $fileName, true,
406
				!empty($filteredStreamUsers[$user]),
407
				$filteredEmailUsers[$user] ?? false,
408
				Files::TYPE_SHARE_CHANGED
409
			);
410
		}
411
	}
412
413
	/**
414
	 * Moving a file from one folder to another
415
	 *
416
	 * @param string $oldPath
417
	 * @param string $newPath
418
	 */
419
	protected function fileMoving($oldPath, $newPath) {
420
		$dirName = dirname($newPath);
421
		$fileName = basename($newPath);
422
		$oldFileName = basename($oldPath);
423
424
		list(, , $fileId) = $this->getSourcePathAndOwner($newPath);
425
		list($parentPath, $parentOwner, $parentId) = $this->getSourcePathAndOwner($dirName);
426
		if ($fileId === 0 || $parentId === 0) {
427
			// Could not find the file for the owner ...
428
			return;
429
		}
430
		$accessList = $this->getUserPathsFromPath($parentPath, $parentOwner);
431
		$affectedUsers = $accessList['users'];
432
		$oldUsers = $this->oldAccessList['users'];
433
434
		$beforeUsers = array_keys($oldUsers);
435
		$afterUsers = array_keys($affectedUsers);
436
437
		$deleteUsers = array_diff($beforeUsers, $afterUsers);
438
		$this->generateDeleteActivities($deleteUsers, $oldUsers, $fileId, $oldFileName);
439
440
		$addUsers = array_diff($afterUsers, $beforeUsers);
441
		$this->generateAddActivities($addUsers, $affectedUsers, $fileId, $fileName);
442
443
		$moveUsers = array_intersect($beforeUsers, $afterUsers);
444
		$this->generateMoveActivities($moveUsers, $oldUsers, $affectedUsers, $fileId, $oldFileName, $parentId, $fileName);
445
446
		$beforeRemotes = $this->oldAccessList['remotes'];
447
		$afterRemotes = $accessList['remotes'];
448
449
		$addRemotes = $deleteRemotes = $moveRemotes = [];
450
		foreach ($afterRemotes as $remote => $info) {
451
			if (isset($beforeRemotes[$remote])) {
452
				// Move
453
				$info['node_path'] = substr($newPath, strlen($info['node_path']));
454
				$info['second_path'] = substr($oldPath, strlen($beforeRemotes[$remote]['node_path']));
455
				$moveRemotes[$remote] = $info;
456
			} else {
457
				$info['node_path'] = substr($newPath, strlen($info['node_path']));
458
				$addRemotes[$remote] = $info;
459
			}
460
		}
461
462
		foreach ($beforeRemotes as $remote => $info) {
463
			if (!isset($afterRemotes[$remote])) {
464
				$info['node_path'] = substr($oldPath, strlen($info['node_path']));
465
				$deleteRemotes[$remote] = $info;
466
			}
467
		}
468
469
		$this->generateRemoteActivity($deleteRemotes, Files::TYPE_SHARE_DELETED, time(), $this->currentUser->getCloudId());
470
		$this->generateRemoteActivity($addRemotes, Files::TYPE_SHARE_CREATED, time(), $this->currentUser->getCloudId());
471
		$this->generateRemoteActivity($moveRemotes, Files::TYPE_SHARE_CHANGED, time(), $this->currentUser->getCloudId());
472
	}
473
474
	/**
475
	 * @param string[] $users
476
	 * @param string[] $pathMap
477
	 * @param int $fileId
478
	 * @param string $oldFileName
479
	 */
480 View Code Duplication
	protected function generateDeleteActivities($users, $pathMap, $fileId, $oldFileName) {
0 ignored issues
show
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...
481
		if (empty($users)) {
482
			return;
483
		}
484
485
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting($users, 'stream', Files::TYPE_SHARE_DELETED);
486
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', Files::TYPE_SHARE_DELETED);
487
488
		foreach ($users as $user) {
489
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
490
				continue;
491
			}
492
493
			$path = $pathMap[$user];
494
495
			if ($user === $this->currentUser->getUID()) {
496
				$userSubject = 'deleted_self';
497
				$userParams = [[$fileId => $path . '/' . $oldFileName]];
498
			} else {
499
				$userSubject = 'deleted_by';
500
				$userParams = [[$fileId => $path . '/' . $oldFileName], $this->currentUser->getUserIdentifier()];
501
			}
502
503
			$this->addNotificationsForUser(
504
				$user, $userSubject, $userParams,
505
				$fileId, $path . '/' . $oldFileName, true,
506
				!empty($filteredStreamUsers[$user]),
507
				$filteredEmailUsers[$user] ?? false,
508
				Files::TYPE_SHARE_DELETED
509
			);
510
		}
511
	}
512
513
	/**
514
	 * @param string[] $users
515
	 * @param string[] $pathMap
516
	 * @param int $fileId
517
	 * @param string $fileName
518
	 */
519 View Code Duplication
	protected function generateAddActivities($users, $pathMap, $fileId, $fileName) {
0 ignored issues
show
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...
520
		if (empty($users)) {
521
			return;
522
		}
523
524
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting($users, 'stream', Files::TYPE_SHARE_CREATED);
525
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', Files::TYPE_SHARE_CREATED);
526
527
		foreach ($users as $user) {
528
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
529
				continue;
530
			}
531
532
			$path = $pathMap[$user];
533
534
			if ($user === $this->currentUser->getUID()) {
535
				$userSubject = 'created_self';
536
				$userParams = [[$fileId => $path . '/' . $fileName]];
537
			} else {
538
				$userSubject = 'created_by';
539
				$userParams = [[$fileId => $path . '/' . $fileName], $this->currentUser->getUserIdentifier()];
540
			}
541
542
			$this->addNotificationsForUser(
543
				$user, $userSubject, $userParams,
544
				$fileId, $path . '/' . $fileName, true,
545
				!empty($filteredStreamUsers[$user]),
546
				$filteredEmailUsers[$user] ?? false,
547
				Files::TYPE_SHARE_CREATED
548
			);
549
		}
550
	}
551
552
	/**
553
	 * @param string[] $users
554
	 * @param string[] $beforePathMap
555
	 * @param string[] $afterPathMap
556
	 * @param int $fileId
557
	 * @param string $oldFileName
558
	 * @param int $newParentId
559
	 * @param string $fileName
560
	 */
561
	protected function generateMoveActivities($users, $beforePathMap, $afterPathMap, $fileId, $oldFileName, $newParentId, $fileName) {
562
		if (empty($users)) {
563
			return;
564
		}
565
566
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting($users, 'stream', Files::TYPE_SHARE_CHANGED);
567
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', Files::TYPE_SHARE_CHANGED);
568
569
		foreach ($users as $user) {
570
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
571
				continue;
572
			}
573
574
			if ($oldFileName === $fileName) {
575
				$userParams = [[$newParentId => $afterPathMap[$user] . '/']];
576
			} else {
577
				$userParams = [[$fileId => $afterPathMap[$user] . '/' . $fileName]];
578
			}
579
580
			if ($user === $this->currentUser->getUID()) {
581
				$userSubject = 'moved_self';
582
			} else {
583
				$userSubject = 'moved_by';
584
				$userParams[] = $this->currentUser->getUserIdentifier();
585
			}
586
			$userParams[] = [$fileId => $beforePathMap[$user] . '/' . $oldFileName];
587
588
			$this->addNotificationsForUser(
589
				$user, $userSubject, $userParams,
590
				$fileId, $afterPathMap[$user] . '/' . $fileName, true,
591
				!empty($filteredStreamUsers[$user]),
592
				$filteredEmailUsers[$user] ?? false,
593
				Files::TYPE_SHARE_CHANGED
594
			);
595
		}
596
	}
597
598
	/**
599
	 * Returns a "username => path" map for all affected users
600
	 *
601
	 * @param string $path
602
	 * @param string $uidOwner
603
	 * @return array
604
	 */
605
	protected function getUserPathsFromPath($path, $uidOwner) {
606
		try {
607
			$node = $this->rootFolder->getUserFolder($uidOwner)->get($path);
608
		} catch (NotFoundException $e) {
0 ignored issues
show
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...
609
			return [];
610
		}
611
612
		if (!$node instanceof Node) {
0 ignored issues
show
The class OCP\Files\Node 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...
613
			return [];
614
		}
615
616
		$accessList = $this->shareHelper->getPathsForAccessList($node);
617
618
		$path = $node->getPath();
619
		$sections = explode('/', $path, 4);
620
621
		$accessList['ownerPath'] = '/';
622
		if (isset($sections[3])) {
623
			// Not the case when a file in root is renamed
624
			$accessList['ownerPath'] .= $sections[3];
625
		}
626
627
		return $accessList;
628
	}
629
630
	/**
631
	 * Return the source
632
	 *
633
	 * @param string $path
634
	 * @return array
635
	 */
636
	protected function getSourcePathAndOwner($path) {
637
		$view = Filesystem::getView();
638
		$owner = $view->getOwner($path);
639
		$owner = !is_string($owner) || $owner === '' ? null : $owner;
640
		$fileId = 0;
641
		$currentUser = $this->currentUser->getUID();
642
643
		if ($owner === null || $owner !== $currentUser) {
644
			/** @var \OCP\Files\Storage\IStorage $storage */
645
			list($storage,) = $view->resolvePath($path);
646
647
			if ($owner !== null && !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
648
				Filesystem::initMountPoints($owner);
649
			} else {
650
				// Probably a remote user, let's try to at least generate activities
651
				// for the current user
652
				if ($currentUser === null) {
653
					list(,$owner,) = explode('/', $view->getAbsolutePath($path), 3);
654
				} else {
655
					$owner = $currentUser;
656
				}
657
			}
658
		}
659
660
		$info = Filesystem::getFileInfo($path);
661
		if ($info !== false) {
662
			$ownerView = new View('/' . $owner . '/files');
663
			$fileId = (int) $info['fileid'];
664
			$path = $ownerView->getPath($fileId);
665
		}
666
667
		return array($path, $owner, $fileId);
668
	}
669
670
	/**
671
	 * Manage sharing events
672
	 * @param array $params The hook params
673
	 */
674 3
	public function share($params) {
675 3
		if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
676 3
			if ((int) $params['shareType'] === Share::SHARE_TYPE_USER) {
677 1
				$this->shareWithUser($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget']);
678 2
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_GROUP) {
679 1
				$this->shareWithGroup($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], (int) $params['id']);
680 1
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_LINK) {
681 1
				$this->shareByLink((int) $params['fileSource'], $params['itemType'], $params['uidOwner']);
682
			}
683
		}
684 3
	}
685
686
	/**
687
	 * Sharing a file or folder with a user
688
	 *
689
	 * @param string $shareWith
690
	 * @param int $fileSource File ID that is being shared
691
	 * @param string $itemType File type that is being shared (file or folder)
692
	 * @param string $fileTarget File path
693
	 */
694 2
	protected function shareWithUser($shareWith, $fileSource, $itemType, $fileTarget) {
695
		// User performing the share
696 2
		$this->shareNotificationForSharer('shared_user_self', $shareWith, $fileSource, $itemType);
697 2 View Code Duplication
		if ($this->currentUser->getUID() !== null) {
0 ignored issues
show
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...
698 2
			$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), 'reshared_user_by', $shareWith, $fileSource, $itemType);
699
		}
700
701
		// New shared user
702 2
		$this->addNotificationsForUser(
703 2
			$shareWith, 'shared_with_by', [[$fileSource => $fileTarget], $this->currentUser->getUserIdentifier()],
704 2
			(int) $fileSource, $fileTarget, $itemType === 'file',
705 2
			$this->userSettings->getUserSetting($shareWith, 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
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...
706 2
			$this->userSettings->getUserSetting($shareWith, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($shareWith, 'setting', 'batchtime') : false
0 ignored issues
show
It seems like $this->userSettings->get...', 'batchtime') : false 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...
707
		);
708 2
	}
709
710
	/**
711
	 * Sharing a file or folder with a group
712
	 *
713
	 * @param string $shareWith
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 $fileTarget File path
717
	 * @param int $shareId The Share ID of this share
718
	 */
719 6
	protected function shareWithGroup($shareWith, $fileSource, $itemType, $fileTarget, $shareId) {
720
		// Members of the new group
721 6
		$group = $this->groupManager->get($shareWith);
722 6
		if (!($group instanceof IGroup)) {
0 ignored issues
show
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...
723 1
			return;
724
		}
725
726
		// User performing the share
727 5
		$this->shareNotificationForSharer('shared_group_self', $shareWith, $fileSource, $itemType);
728 5 View Code Duplication
		if ($this->currentUser->getUID() !== null) {
0 ignored issues
show
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...
729 5
			$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), 'reshared_group_by', $shareWith, $fileSource, $itemType);
730
		}
731
732 5
		$offset = 0;
733 5
		$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
734 5
		while (!empty($users)) {
735 4
			$this->addNotificationsForGroupUsers($users, 'shared_with_by', $fileSource, $itemType, $fileTarget, $shareId);
736 4
			$offset += self::USER_BATCH_SIZE;
737 4
			$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
738
		}
739 5
	}
740
741
	/**
742
	 * Sharing a file or folder via link/public
743
	 *
744
	 * @param int $fileSource File ID that is being shared
745
	 * @param string $itemType File type that is being shared (file or folder)
746
	 * @param string $linkOwner
747
	 */
748 2
	protected function shareByLink($fileSource, $itemType, $linkOwner) {
749 2
		$this->view->chroot('/' . $linkOwner . '/files');
750
751
		try {
752 2
			$path = $this->view->getPath($fileSource);
753 1
		} catch (NotFoundException $e) {
0 ignored issues
show
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($linkOwner, 'reshared_link_by', '', $fileSource, $itemType);
758
759 1
		$this->addNotificationsForUser(
760 1
			$linkOwner, 'shared_link_self', [[$fileSource => $path]],
761 1
			(int) $fileSource, $path, $itemType === 'file',
762 1
			$this->userSettings->getUserSetting($linkOwner, 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
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($linkOwner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($linkOwner, 'setting', 'batchtime') : false
0 ignored issues
show
It seems like $this->userSettings->get...', 'batchtime') : false 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
		);
765 1
	}
766
767
	/**
768
	 * Manage unsharing events
769
	 * @param IShare $share
770
	 * @throws \OCP\Files\NotFoundException
771
	 */
772
	public function unShare(IShare $share) {
773
		if (in_array($share->getNodeType(), ['file', 'folder'], true)) {
774
			if ($share->getShareType() === Share::SHARE_TYPE_USER) {
775
				$this->unshareFromUser($share);
776
			} else if ($share->getShareType() === Share::SHARE_TYPE_GROUP) {
777
				$this->unshareFromGroup($share);
778
			} else if ($share->getShareType() === Share::SHARE_TYPE_LINK) {
779
				$this->unshareLink($share);
780
			}
781
		}
782
	}
783
784
	/**
785
	 * Unharing a file or folder from a user
786
	 *
787
	 * @param IShare $share
788
	 * @throws \OCP\Files\NotFoundException
789
	 */
790
	protected function unshareFromUser(IShare $share) {
791
		if ($share->getSharedWith() === $this->currentUser->getUID()) {
792
			$this->selfUnshareFromUser($share);
793
			return;
794
		}
795
796
		// User performing the share
797
		$this->shareNotificationForSharer('unshared_user_self', $share->getSharedWith(), $share->getNodeId(), $share->getNodeType());
798
799
		// Owner
800 View Code Duplication
		if ($this->currentUser->getUID() !== null) {
0 ignored issues
show
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...
801
			$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), 'unshared_user_by', $share->getSharedWith(), $share->getNodeId(), $share->getNodeType());
802
		}
803
804
		// Recipient
805
		$this->addNotificationsForUser(
806
			$share->getSharedWith(), 'unshared_by', [[$share->getNodeId() => $share->getTarget()], $this->currentUser->getUserIdentifier()],
807
			$share->getNodeId(), $share->getTarget(), $share->getNodeType() === 'file',
808
			$this->userSettings->getUserSetting($share->getSharedWith(), 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
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...
809
			$this->userSettings->getUserSetting($share->getSharedWith(), 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($share->getSharedWith(), 'setting', 'batchtime') : false
0 ignored issues
show
It seems like $this->userSettings->get...', 'batchtime') : false 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...
810
		);
811
	}
812
813
	/**
814
	 * Unharing a file or folder from a user
815
	 *
816
	 * @param IShare $share
817
	 * @throws \OCP\Files\NotFoundException
818
	 */
819
	protected function selfUnshareFromUser(IShare $share) {
820
		// User performing the share
821
		$this->shareNotificationForSharer('self_unshared', $share->getSharedWith(), $share->getNodeId(), $share->getNodeType());
822
823
		// Owner
824 View Code Duplication
		if ($this->currentUser->getUID() !== null) {
0 ignored issues
show
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...
825
			$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), 'self_unshared_by', $share->getSharedWith(), $share->getNodeId(), $share->getNodeType());
826
		}
827
	}
828
829
	/**
830
	 * Unsharing a file or folder from a group
831
	 *
832
	 * @param IShare $share
833
	 * @throws \OCP\Files\NotFoundException
834
	 */
835
	protected function unshareFromGroup(IShare $share) {
836
		// Members of the new group
837
		$group = $this->groupManager->get($share->getSharedWith());
838
		if (!($group instanceof IGroup)) {
0 ignored issues
show
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...
839
			return;
840
		}
841
842
		// User performing the share
843
		$this->shareNotificationForSharer('unshared_group_self', $share->getSharedWith(), $share->getNodeId(), $share->getNodeType());
844 View Code Duplication
		if ($this->currentUser->getUID() !== null) {
0 ignored issues
show
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...
845
			$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), 'unshared_group_by', $share->getSharedWith(), $share->getNodeId(), $share->getNodeType());
846
		}
847
848
		$offset = 0;
849
		$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
850
		while (!empty($users)) {
851
			$this->addNotificationsForGroupUsers($users, 'unshared_by', $share->getNodeId(), $share->getNodeType(), $share->getTarget(), $share->getId());
852
			$offset += self::USER_BATCH_SIZE;
853
			$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
854
		}
855
	}
856
857
	/**
858
	 * Sharing a file or folder via link/public
859
	 *
860
	 * @param IShare $share
861
	 * @throws \OCP\Files\NotFoundException
862
	 */
863
	protected function unshareLink(IShare $share) {
864
		$owner = $share->getSharedBy();
865
		if ($this->currentUser->getUID() === null) {
866
			// Link expired
867
			$actionSharer = 'link_expired';
868
			$actionOwner = 'link_by_expired';
869
		} else {
870
			$actionSharer = 'unshared_link_self';
871
			$actionOwner = 'unshared_link_by';
872
		}
873
874
		$this->addNotificationsForUser(
875
			$owner, $actionSharer, [[$share->getNodeId() => $share->getTarget()]],
876
			$share->getNodeId(), $share->getTarget(), $share->getNodeType() === 'file',
877
			$this->userSettings->getUserSetting($owner, 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
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...
878
			$this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : false
0 ignored issues
show
It seems like $this->userSettings->get...', 'batchtime') : false 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...
879
		);
880
881
		if ($share->getSharedBy() !== $share->getShareOwner()) {
882
			$owner = $share->getShareOwner();
883
			$this->addNotificationsForUser(
884
				$owner, $actionOwner, [[$share->getNodeId() => $share->getTarget()], $share->getSharedBy()],
885
				$share->getNodeId(), $share->getTarget(), $share->getNodeType() === 'file',
886
				$this->userSettings->getUserSetting($owner, 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
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...
887
				$this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : false
0 ignored issues
show
It seems like $this->userSettings->get...', 'batchtime') : false 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...
888
			);
889
		}
890
	}
891
892
	/**
893
	 * @param IUser[] $usersInGroup
894
	 * @param string $actionUser
895
	 * @param int $fileSource File ID that is being shared
896
	 * @param string $itemType File type that is being shared (file or folder)
897
	 * @param string $fileTarget File path
898
	 * @param int $shareId The Share ID of this share
899
	 */
900 4
	protected function addNotificationsForGroupUsers(array $usersInGroup, $actionUser, $fileSource, $itemType, $fileTarget, $shareId) {
901 4
		$affectedUsers = [];
902
903 4
		foreach ($usersInGroup as $user) {
904 4
			$affectedUsers[$user->getUID()] = $fileTarget;
905
		}
906
907
		// Remove the triggering user, we already managed his notifications
908 4
		unset($affectedUsers[$this->currentUser->getUID()]);
909
910 4
		if (empty($affectedUsers)) {
911 1
			return;
912
		}
913
914 3
		$userIds = array_keys($affectedUsers);
915 3
		$filteredStreamUsersInGroup = $this->userSettings->filterUsersBySetting($userIds, 'stream', Files_Sharing::TYPE_SHARED);
916 3
		$filteredEmailUsersInGroup = $this->userSettings->filterUsersBySetting($userIds, 'email', Files_Sharing::TYPE_SHARED);
917
918 3
		$affectedUsers = $this->fixPathsForShareExceptions($affectedUsers, $shareId);
919 3
		foreach ($affectedUsers as $user => $path) {
920 3
			if (empty($filteredStreamUsersInGroup[$user]) && empty($filteredEmailUsersInGroup[$user])) {
921 2
				continue;
922
			}
923
924 1
			$this->addNotificationsForUser(
925 1
				$user, $actionUser, [[$fileSource => $path], $this->currentUser->getUserIdentifier()],
926 1
				$fileSource, $path, ($itemType === 'file'),
927 1
				!empty($filteredStreamUsersInGroup[$user]),
928 1
				$filteredEmailUsersInGroup[$user] ?? false
929
			);
930
		}
931 3
	}
932
933
	/**
934
	 * Check when there was a naming conflict and the target is different
935
	 * for some of the users
936
	 *
937
	 * @param array $affectedUsers
938
	 * @param int $shareId
939
	 * @return mixed
940
	 */
941
	protected function fixPathsForShareExceptions(array $affectedUsers, $shareId) {
942
		$queryBuilder = $this->connection->getQueryBuilder();
943
		$queryBuilder->select(['share_with', 'file_target'])
944
			->from('share')
945
			->where($queryBuilder->expr()->eq('parent', $queryBuilder->createParameter('parent')))
946
			->setParameter('parent', (int) $shareId);
947
		$query = $queryBuilder->execute();
948
949
		while ($row = $query->fetch()) {
950
			$affectedUsers[$row['share_with']] = $row['file_target'];
951
		}
952
953
		return $affectedUsers;
954
	}
955
956
	/**
957
	 * Add notifications for the user that shares a file/folder
958
	 *
959
	 * @param string $subject
960
	 * @param string $shareWith
961
	 * @param int $fileSource
962
	 * @param string $itemType
963
	 */
964 2
	protected function shareNotificationForSharer($subject, $shareWith, $fileSource, $itemType) {
965 2
		$sharer = $this->currentUser->getUID();
966 2
		if ($sharer === null) {
967
			return;
968
		}
969
970 2
		$this->view->chroot('/' . $sharer . '/files');
971
972
		try {
973 2
			$path = $this->view->getPath($fileSource);
974 1
		} catch (NotFoundException $e) {
0 ignored issues
show
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...
975 1
			return;
976
		}
977
978 1
		$this->addNotificationsForUser(
979 1
			$sharer, $subject, [[$fileSource => $path], $shareWith],
980 1
			$fileSource, $path, ($itemType === 'file'),
981 1
			$this->userSettings->getUserSetting($sharer, 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
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...
982 1
			$this->userSettings->getUserSetting($sharer, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($sharer, 'setting', 'batchtime') : false
0 ignored issues
show
It seems like $this->userSettings->get...', 'batchtime') : false 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...
983
		);
984 1
	}
985
986
	/**
987
	 * Add notifications for the user that shares a file/folder
988
	 *
989
	 * @param string $owner
990
	 * @param string $subject
991
	 * @param string $shareWith
992
	 * @param int $fileSource
993
	 * @param string $itemType
994
	 */
995 2
	protected function reshareNotificationForSharer($owner, $subject, $shareWith, $fileSource, $itemType) {
996 2
		$this->view->chroot('/' . $owner . '/files');
997
998
		try {
999 2
			$path = $this->view->getPath($fileSource);
1000 1
		} catch (NotFoundException $e) {
0 ignored issues
show
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...
1001 1
			return;
1002
		}
1003
1004 1
		$this->addNotificationsForUser(
1005 1
			$owner, $subject, [[$fileSource => $path], $this->currentUser->getUserIdentifier(), $shareWith],
1006 1
			$fileSource, $path, ($itemType === 'file'),
1007 1
			$this->userSettings->getUserSetting($owner, 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
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...
1008 1
			$this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : false
0 ignored issues
show
It seems like $this->userSettings->get...', 'batchtime') : false 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...
1009
		);
1010 1
	}
1011
1012
	/**
1013
	 * Add notifications for the owners whose files have been reshared
1014
	 *
1015
	 * @param string $currentOwner
1016
	 * @param string $subject
1017
	 * @param string $shareWith
1018
	 * @param int $fileSource
1019
	 * @param string $itemType
1020
	 */
1021 10
	protected function shareNotificationForOriginalOwners($currentOwner, $subject, $shareWith, $fileSource, $itemType) {
1022
		// Get the full path of the current user
1023 10
		$this->view->chroot('/' . $currentOwner . '/files');
1024
1025
		try {
1026 10
			$path = $this->view->getPath($fileSource);
1027 1
		} catch (NotFoundException $e) {
0 ignored issues
show
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...
1028 1
			return;
1029
		}
1030
1031
		/**
1032
		 * Get the original owner and his path
1033
		 */
1034 9
		$owner = $this->view->getOwner($path);
1035 9
		if ($owner !== $currentOwner) {
1036 7
			$this->reshareNotificationForSharer($owner, $subject, $shareWith, $fileSource, $itemType);
1037
		}
1038
1039
		/**
1040
		 * Get the sharee who shared the item with the currentUser
1041
		 */
1042 9
		$this->view->chroot('/' . $currentOwner . '/files');
1043 9
		$mount = $this->view->getMount($path);
1044 9
		if (!($mount instanceof IMountPoint)) {
0 ignored issues
show
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...
1045 1
			return;
1046
		}
1047
1048 8
		$storage = $mount->getStorage();
1049 8
		if (!$storage->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) {
1050 1
			return;
1051
		}
1052
1053
		/** @var \OCA\Files_Sharing\SharedStorage $storage */
1054 7
		$shareOwner = $storage->getSharedFrom();
1055 7
		if ($shareOwner === '' || $shareOwner === null || $shareOwner === $owner || $shareOwner === $currentOwner) {
1056 5
			return;
1057
		}
1058
1059 2
		$this->reshareNotificationForSharer($shareOwner, $subject, $shareWith, $fileSource, $itemType);
1060 2
	}
1061
1062
	/**
1063
	 * Adds the activity and email for a user when the settings require it
1064
	 *
1065
	 * @param string $user
1066
	 * @param string $subject
1067
	 * @param array $subjectParams
1068
	 * @param int $fileId
1069
	 * @param string $path
1070
	 * @param bool $isFile If the item is a file, we link to the parent directory
1071
	 * @param bool $streamSetting
1072
	 * @param int $emailSetting
1073
	 * @param string $type
1074
	 */
1075 11
	protected function addNotificationsForUser($user, $subject, $subjectParams, $fileId, $path, $isFile, $streamSetting, $emailSetting, $type = Files_Sharing::TYPE_SHARED) {
1076 11
		if (!$streamSetting && !$emailSetting) {
1077 1
			return;
1078
		}
1079
1080 10
		$user = (string)$user;
1081 10
		$selfAction = $user === $this->currentUser->getUID();
1082 10
		$app = $type === Files_Sharing::TYPE_SHARED ? 'files_sharing' : 'files';
1083 10
		$link = $this->urlGenerator->linkToRouteAbsolute('files.view.index', array(
1084 10
			'dir' => ($isFile) ? dirname($path) : $path,
1085
		));
1086
1087 10
		$objectType = ($fileId) ? 'files' : '';
1088
1089 10
		$event = $this->manager->generateEvent();
1090
		try {
1091 10
			$event->setApp($app)
1092 10
				->setType($type)
1093 10
				->setAffectedUser($user)
1094 10
				->setTimestamp(time())
1095 10
				->setSubject($subject, $subjectParams)
1096 10
				->setObject($objectType, $fileId, $path)
1097 10
				->setLink($link);
1098
1099 10
			if ($this->currentUser->getUID() !== null) {
1100
				// Allow this to be empty for guests
1101 10
				$event->setAuthor($this->currentUser->getUID());
1102
			}
1103
		} catch (\InvalidArgumentException $e) {
1104
			$this->logger->logException($e);
1105
		}
1106
1107
		// Add activity to stream
1108 10
		if ($streamSetting && (!$selfAction || $this->userSettings->getUserSetting($this->currentUser->getUID(), 'setting', 'self'))) {
1109 3
			$this->activityData->send($event);
1110
		}
1111
1112
		// Add activity to mail queue
1113 10
		if ($emailSetting !== false && (!$selfAction || $this->userSettings->getUserSetting($this->currentUser->getUID(), 'setting', 'selfemail'))) {
1114 5
			$latestSend = time() + $emailSetting;
1115 5
			$this->activityData->storeMail($event, $latestSend);
1116
		}
1117 10
	}
1118
}
1119