Completed
Pull Request — master (#558)
by
unknown
20:45
created

FilesHooks::fileUpdate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
c 0
b 0
f 0
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * @author Frank Karlitschek <[email protected]>
4
 * @author Joas Schilling <[email protected]>
5
 * @author Thomas Müller <[email protected]>
6
 *
7
 * @copyright Copyright (c) 2016, ownCloud, Inc.
8
 * @license AGPL-3.0
9
 *
10
 * This code is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License, version 3,
12
 * as published by the Free Software Foundation.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License, version 3,
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
21
 *
22
 */
23
24
namespace OCA\Activity;
25
26
use OC\Files\Filesystem;
27
use OC\Files\View;
28
use OCA\Activity\Extension\Files;
29
use OCA\Activity\Extension\Files_Sharing;
30
use OCP\Activity\IManager;
31
use OCP\Files\Mount\IMountPoint;
32
use OCP\Files\NotFoundException;
33
use OCP\IDBConnection;
34
use OCP\IGroup;
35
use OCP\IGroupManager;
36
use OCP\IURLGenerator;
37
use OCP\IUser;
38
use OCP\Share;
39
40
/**
41
 * The class to handle the filesystem hooks
42
 */
43
class FilesHooks {
44
	const USER_BATCH_SIZE = 50;
45
46
	/** @var \OCP\Activity\IManager */
47
	protected $manager;
48
49
	/** @var \OCA\Activity\Data */
50
	protected $activityData;
51
52
	/** @var \OCA\Activity\UserSettings */
53
	protected $userSettings;
54
55
	/** @var \OCP\IGroupManager */
56
	protected $groupManager;
57
58
	/** @var \OCP\IDBConnection */
59
	protected $connection;
60
61
	/** @var \OC\Files\View */
62
	protected $view;
63
64
	/** @var IURLGenerator */
65
	protected $urlGenerator;
66
67
	/** @var string|false */
68
	protected $currentUser;
69
70
	/** @var array */
71
	protected $renameInfo = [];
72
73
	/**
74
	 * Constructor
75
	 *
76
	 * @param IManager $manager
77
	 * @param Data $activityData
78
	 * @param UserSettings $userSettings
79
	 * @param IGroupManager $groupManager
80
	 * @param View $view
81
	 * @param IDBConnection $connection
82
	 * @param IURLGenerator $urlGenerator
83
	 * @param string|false $currentUser
84
	 */
85 49
	public function __construct(IManager $manager, Data $activityData, UserSettings $userSettings, IGroupManager $groupManager, View $view, IDBConnection $connection, IURLGenerator $urlGenerator, $currentUser) {
86 49
		$this->manager = $manager;
87 49
		$this->activityData = $activityData;
88 49
		$this->userSettings = $userSettings;
89 49
		$this->groupManager = $groupManager;
90 49
		$this->view = $view;
91 49
		$this->connection = $connection;
92 49
		$this->urlGenerator = $urlGenerator;
93 49
		$this->currentUser = $currentUser;
94 49
	}
95
96
	/**
97
	 * @return string|false Current UserID if logged in, false otherwise
98
	 */
99 2
	protected function getCurrentUser() {
100 2
		return $this->currentUser;
101
	}
102
103
	/**
104
	 * Store the create hook events
105
	 * @param string $path Path of the file that has been created
106
	 */
107 2
	public function fileCreate($path) {
108 2
		if ($this->getCurrentUser() !== false) {
109 1
			$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CREATED, 'created_self', 'created_by');
110
		} else {
111 1
			$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CREATED, '', 'created_public');
112
		}
113 2
	}
114
115
	/**
116
	 * Store the update hook events
117
	 * @param string $path Path of the file that has been modified
118
	 */
119 1
	public function fileUpdate($path) {
120 1
		$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CHANGED, 'changed_self', 'changed_by');
121 1
	}
122
123
	/**
124
	 * Store the delete hook events
125
	 * @param string $path Path of the file that has been deleted
126
	 */
127 1
	public function fileDelete($path) {
128 1
		$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_DELETED, 'deleted_self', 'deleted_by');
129 1
	}
130
131
	/**
132
	 * Store the rename hook events
133
	 * @param string $oldPath Path of the file before rename
134
	 * @param string $newPath Path of the file after rename
135
	 */
136
	public function fileBeforeRename($oldPath, $newPath) {
0 ignored issues
show
Unused Code introduced by
The parameter $newPath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
137
		// Do not add activities for .part-files
138
		if (substr($oldPath, -5) === '.part') {
139
			return;
140
		}
141
		list($filePath, $uidOwner, $fileId) = $this->getSourcePathAndOwner($oldPath);
142
		if (!$fileId) {
143
			// no owner, possibly deleted or unknown
144
			// skip notifications
145
			return;
146
		}
147
		$affectedUsers = $this->getUserPathsFromPath($filePath, $uidOwner);
148
149
		$this->renameInfo[$oldPath] = [
150
			'oldAffectedUsers' => $affectedUsers,
151
			'oldPath' => $filePath,
152
			'oldUidOwner' => $uidOwner,
153
			'oldFileId' => $fileId,
154
		];
155
	}
156
157
	/**
158
	 * Store the rename hook events
159
	 * @param string $oldPath Path of the file before rename
160
	 * @param string $newPath Path of the file after rename
161
	 */
162
	public function fileRename($oldPath, $newPath) {
163
		// Do not add activities for .part-files
164
		if (substr($oldPath, -5) === '.part') {
165
			return;
166
		}
167
168
		list($filePath, $uidOwner, $fileId) = $this->getSourcePathAndOwner($newPath);
169
		if (!$fileId) {
170
			// no owner, possibly deleted or unknown
171
			// skip notifications
172
			return;
173
		}
174
		$affectedUsers = $this->getUserPathsFromPath($filePath, $uidOwner);
175
176
		if (!isset($this->renameInfo[$oldPath])) {
177
			// no pre hook? can't do anything
178
			return;
179
		}
180
181
		$renameInfo = $this->renameInfo[$oldPath];
182
		unset($this->renameInfo[$oldPath]);
183
184
		if ($fileId !== $renameInfo['oldFileId']) {
185
			// file id changed, we can't point to it accurately
186
			return;
187
		}
188
189
		if ($this->getCurrentUser() !== false) {
190
			$subject = 'renamed_self';
191
			$subjectBy = 'renamed_by';
192
		} else {
193
			$subject = '';
194
			$subjectBy = 'renamed_public';
195
		}
196
		$this->addNotificationsForFileRename(
197
			$renameInfo['oldPath'],
198
			$renameInfo['oldUidOwner'],
199
			$renameInfo['oldAffectedUsers'],
200
			$filePath,
201
			$uidOwner,
202
			$affectedUsers,
0 ignored issues
show
Documentation introduced by
$affectedUsers is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
203
			$fileId,
204
			$subject,
205
			$subjectBy
206
		);
207
	}
208
209
	/**
210
	 * Store the restore hook events
211
	 * @param string $path Path of the file that has been restored
212
	 */
213 1
	public function fileRestore($path) {
214 1
		$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_RESTORED, 'restored_self', 'restored_by');
215 1
	}
216
217
	/**
218
	 * Creates the entries for file actions on $file_path
219
	 *
220
	 * @param string $filePath         The file that is being changed
221
	 * @param int    $activityType     The activity type
222
	 * @param string $subject          The subject for the actor
223
	 * @param string $subjectBy        The subject for other users (with "by $actor")
224
	 */
225 3
	protected function addNotificationsForFileAction($filePath, $activityType, $subject, $subjectBy) {
226
		// Do not add activities for .part-files
227 3
		if (substr($filePath, -5) === '.part') {
228 1
			return;
229
		}
230
231 2
		list($filePath, $uidOwner, $fileId) = $this->getSourcePathAndOwner($filePath);
232 2
		if (!$fileId) {
233
			// no owner, possibly deleted or unknown
234
			// skip notifications
235
			return;
236
		}
237 2
		$affectedUsers = $this->getUserPathsFromPath($filePath, $uidOwner);
238 2
		$filteredStreamUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'stream', $activityType);
239 2
		$filteredEmailUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'email', $activityType);
240
241 2
		foreach ($affectedUsers as $user => $path) {
242 2
			if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
243 1
				continue;
244
			}
245
246 2
			if ($user === $this->currentUser) {
247 1
				$userSubject = $subject;
248 1
				$userParams = [[$fileId => $filePath], [$fileId => $filePath]];
249
			} else {
250 1
				$userSubject = $subjectBy;
251 1
				$userParams = [[$fileId => $filePath], [$fileId => $filePath], $this->currentUser];
252
			}
253
254 2
			$this->addNotificationsForUser(
255
				$user, $userSubject, $userParams,
256 2
				$fileId, $path, true,
257 2
				!empty($filteredStreamUsers[$user]),
258 2
				!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
259
				$activityType
260
			);
261
		}
262
	}
263
264
	/**
265
	 * Creates the entries for file rename from $oldPath to $newPath
266
	 *
267
	 * @param string $oldPath          The source path including user name, relative to the data dir
268
	 * @param string $oldUidOwner      The source path owner
269
	 * @param string $oldAffectedUsers The affected users from the old path
270
	 * @param string $newPath          The target path including user name, relative to the data dir
271
	 * @param string $newUidOwner      The target path owner
272
	 * @param string $newAffectedUsers The affected users from the new path
273
	 * @param string $fileId           The file id
274
	 * @param string $subject          The subject for the actor
275
	 * @param string $subjectBy        The subject for other users (with "by $actor")
276
	 */
277
	protected function addNotificationsForFileRename(
278
		$oldPath,
279
		$oldUidOwner,
0 ignored issues
show
Unused Code introduced by
The parameter $oldUidOwner is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
280
		$oldAffectedUsers,
281
		$newPath,
282
		$newUidOwner,
0 ignored issues
show
Unused Code introduced by
The parameter $newUidOwner is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
283
		$newAffectedUsers,
284
		$fileId,
285
		$subject,
286
		$subjectBy
287
	) {
288
		$activityType = Files::TYPE_SHARE_RENAMED;
289
290
		// FIXME: use more efficient algo
291
		$types = [
292
			// users seeing a rename, both source and target visible
293
			Files::TYPE_SHARE_RENAMED => array_intersect_key($oldAffectedUsers, $newAffectedUsers),
294
			// users seeing a move in, only target visible
295
			Files::TYPE_SHARE_MOVEDIN => array_diff_key($newAffectedUsers, $oldAffectedUsers),
296
			// users seeing a move out, only target visible
297
			Files::TYPE_SHARE_MOVEDOUT => array_diff_key($oldAffectedUsers, $newAffectedUsers),
298
		];
299
300
		foreach ($types as $activityType => $affectedUsers) {
301
			$filteredStreamUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'stream', $activityType);
302
			$filteredEmailUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'email', $activityType);
303
			foreach ($affectedUsers as $user => $path) {
304
				if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
305
					continue;
306
				}
307
308
				// use $oldPath as parameter because that one cannot be resolved back
309
				// and fileid can be used to find the new path when displaying
310
				if ($user === $this->currentUser) {
311
					$userSubject = $subject;
312
					$userParams = [[$fileId => $oldPath]];
313
				} else {
314
					$userSubject = $subjectBy;
315
					$userParams = [[$fileId => $oldPath], $this->currentUser];
316
				}
317
318
				$this->addNotificationsForUser(
319
					$user,
320
					$userSubject,
321
					$userParams,
322
					$fileId,
323
					$newPath,
324
					true,
325
					!empty($filteredStreamUsers[$user]),
326
					!empty($filteredEmailUsers[$user]) ? $filteredEmailUsers[$user] : 0,
327
					$activityType
328
				);
329
			}
330
		}
331
	}
332
333
	/**
334
	 * Returns a "username => path" map for all affected users
335
	 *
336
	 * @param string $path
337
	 * @param string $uidOwner
338
	 * @return array
339
	 */
340
	protected function getUserPathsFromPath($path, $uidOwner) {
341
		return Share::getUsersSharingFile($path, $uidOwner, true, true);
342
	}
343
344
	/**
345
	 * Return the source
346
	 *
347
	 * @param string $path
348
	 * @return array
349
	 */
350
	protected function getSourcePathAndOwner($path) {
351
		$currentUserView = Filesystem::getView();
352
		$uidOwner = $currentUserView->getOwner($path);
353
		$fileId = 0;
354
355
		if ($uidOwner !== $this->currentUser) {
356
			list($storage, $internalPath) = $currentUserView->resolvePath($path);
0 ignored issues
show
Unused Code introduced by
The assignment to $internalPath is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
357
			if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
358
				// for federated shares we don't have access to the remote user, use the current one
359
				// which will also make it use the matching local "shared::" federated share storage instead
360
				$uidOwner = $this->currentUser;
361
			} else {
362
				Filesystem::initMountPoints($uidOwner);
363
			}
364
		}
365
		$info = Filesystem::getFileInfo($path);
366
		if ($info !== false) {
367
			$ownerView = new View('/' . $uidOwner . '/files');
368
			$fileId = (int) $info['fileid'];
369
			$path = $ownerView->getPath($fileId);
370
		}
371
372
		return array($path, $uidOwner, $fileId);
373
	}
374
375
	/**
376
	 * Manage sharing events
377
	 * @param array $params The hook params
378
	 */
379 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...
380 3
		if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
381 3
			if ((int) $params['shareType'] === Share::SHARE_TYPE_USER) {
382 1
				$this->shareFileOrFolderWithUser($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], true);
383 2
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_GROUP) {
384 1
				$this->shareFileOrFolderWithGroup($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], (int) $params['id'], true);
385 1
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_LINK) {
386 1
				$this->shareFileOrFolderByLink((int) $params['fileSource'], $params['itemType'], $params['uidOwner'], true);
387
			}
388
		}
389 3
	}
390
391
	/**
392
	 * Manage sharing events
393
	 * @param array $params The hook params
394
	 */
395 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...
396
		if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
397
			if ((int) $params['shareType'] === Share::SHARE_TYPE_USER) {
398
				$this->shareFileOrFolderWithUser($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], false);
399
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_GROUP) {
400
				$this->shareFileOrFolderWithGroup($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], (int) $params['id'], false);
401
			} else if ((int) $params['shareType'] === Share::SHARE_TYPE_LINK) {
402
				$this->shareFileOrFolderByLink((int) $params['fileSource'], $params['itemType'], $params['uidOwner'], false);
403
			}
404
		}
405
	}
406
407
	/**
408
	 * Sharing a file or folder with a user
409
	 *
410
	 * @param string $shareWith
411
	 * @param int $fileSource File ID that is being shared
412
	 * @param string $itemType File type that is being shared (file or folder)
413
	 * @param string $fileTarget File path
414
	 * @param bool $isSharing True if sharing, false if unsharing
415
	 */
416 2
	protected function shareFileOrFolderWithUser($shareWith, $fileSource, $itemType, $fileTarget, $isSharing) {
417 2
		if ($isSharing) {
418 2
			$actionSharer = 'shared_user_self';
419 2
			$actionOwner = 'reshared_user_by';
420 2
			$actionUser = 'shared_with_by';
421
		} else {
422
			$actionSharer = 'unshared_user_self';
423
			$actionOwner = 'unshared_user_by';
424
			$actionUser = 'unshared_by';
425
		}
426
427
		// User performing the share
428 2
		$this->shareNotificationForSharer($actionSharer, $shareWith, $fileSource, $itemType);
429 2
		$this->shareNotificationForOriginalOwners($this->currentUser, $actionOwner, $shareWith, $fileSource, $itemType);
0 ignored issues
show
Security Bug introduced by
It seems like $this->currentUser can also be of type false; however, OCA\Activity\FilesHooks:...tionForOriginalOwners() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
430
431
		// New shared user
432 2
		$this->addNotificationsForUser(
433 2
			$shareWith, $actionUser, [[$fileSource => $fileTarget], $this->currentUser],
434 2
			(int) $fileSource, $fileTarget, ($itemType === 'file'),
435 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...
436 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...
437
		);
438 2
	}
439
440
	/**
441
	 * Sharing a file or folder with a group
442
	 *
443
	 * @param string $shareWith
444
	 * @param int $fileSource File ID that is being shared
445
	 * @param string $itemType File type that is being shared (file or folder)
446
	 * @param string $fileTarget File path
447
	 * @param int $shareId The Share ID of this share
448
	 * @param bool $isSharing True if sharing, false if unsharing
449
	 */
450 6
	protected function shareFileOrFolderWithGroup($shareWith, $fileSource, $itemType, $fileTarget, $shareId, $isSharing) {
451 6
		if ($isSharing) {
452 6
			$actionSharer = 'shared_group_self';
453 6
			$actionOwner = 'reshared_group_by';
454 6
			$actionUser = 'shared_with_by';
455
		} else {
456
			$actionSharer = 'unshared_group_self';
457
			$actionOwner = 'unshared_group_by';
458
			$actionUser = 'unshared_by';
459
		}
460
461
		// Members of the new group
462 6
		$group = $this->groupManager->get($shareWith);
463 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...
464 1
			return;
465
		}
466
467
		// User performing the share
468 5
		$this->shareNotificationForSharer($actionSharer, $shareWith, $fileSource, $itemType);
469 5
		$this->shareNotificationForOriginalOwners($this->currentUser, $actionOwner, $shareWith, $fileSource, $itemType);
0 ignored issues
show
Security Bug introduced by
It seems like $this->currentUser can also be of type false; however, OCA\Activity\FilesHooks:...tionForOriginalOwners() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
470
471 5
		$offset = 0;
472 5
		$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
473 5
		while (!empty($users)) {
474 4
			$this->addNotificationsForGroupUsers($users, $actionUser, $fileSource, $itemType, $fileTarget, $shareId);
475 4
			$offset += self::USER_BATCH_SIZE;
476 4
			$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
477
		}
478 5
	}
479
480
	/**
481
	 * @param IUser[] $usersInGroup
482
	 * @param string $actionUser
483
	 * @param int $fileSource File ID that is being shared
484
	 * @param string $itemType File type that is being shared (file or folder)
485
	 * @param string $fileTarget File path
486
	 * @param int $shareId The Share ID of this share
487
	 */
488 4
	protected function addNotificationsForGroupUsers(array $usersInGroup, $actionUser, $fileSource, $itemType, $fileTarget, $shareId) {
489 4
		$affectedUsers = [];
490
491 4
		foreach ($usersInGroup as $user) {
492 4
			$affectedUsers[$user->getUID()] = $fileTarget;
493
		}
494
495
		// Remove the triggering user, we already managed his notifications
496 4
		unset($affectedUsers[$this->currentUser]);
497
498 4
		if (empty($affectedUsers)) {
499 1
			return;
500
		}
501
502 3
		$userIds = array_keys($affectedUsers);
503 3
		$filteredStreamUsersInGroup = $this->userSettings->filterUsersBySetting($userIds, 'stream', Files_Sharing::TYPE_SHARED);
504 3
		$filteredEmailUsersInGroup = $this->userSettings->filterUsersBySetting($userIds, 'email', Files_Sharing::TYPE_SHARED);
505
506 3
		$affectedUsers = $this->fixPathsForShareExceptions($affectedUsers, $shareId);
507 3
		foreach ($affectedUsers as $user => $path) {
508 3
			if (empty($filteredStreamUsersInGroup[$user]) && empty($filteredEmailUsersInGroup[$user])) {
509 2
				continue;
510
			}
511
512 1
			$this->addNotificationsForUser(
513 1
				$user, $actionUser, [[$fileSource => $path], $this->currentUser],
514 1
				$fileSource, $path, ($itemType === 'file'),
515 1
				!empty($filteredStreamUsersInGroup[$user]),
516 1
				!empty($filteredEmailUsersInGroup[$user]) ? $filteredEmailUsersInGroup[$user] : 0
517
			);
518
		}
519 3
	}
520
521
	/**
522
	 * Check when there was a naming conflict and the target is different
523
	 * for some of the users
524
	 *
525
	 * @param array $affectedUsers
526
	 * @param int $shareId
527
	 * @return mixed
528
	 */
529
	protected function fixPathsForShareExceptions(array $affectedUsers, $shareId) {
530
		$queryBuilder = $this->connection->getQueryBuilder();
531
		$queryBuilder->select(['share_with', 'file_target'])
532
			->from('share')
533
			->where($queryBuilder->expr()->eq('parent', $queryBuilder->createParameter('parent')))
534
			->setParameter('parent', (int) $shareId);
535
		$query = $queryBuilder->execute();
536
537
		while ($row = $query->fetch()) {
538
			$affectedUsers[$row['share_with']] = $row['file_target'];
539
		}
540
541
		return $affectedUsers;
542
	}
543
544
	/**
545
	 * Sharing a file or folder via link/public
546
	 *
547
	 * @param int $fileSource File ID that is being shared
548
	 * @param string $itemType File type that is being shared (file or folder)
549
	 * @param string $linkOwner
550
	 * @param bool $isSharing True if sharing, false if unsharing
551
	 */
552 2
	protected function shareFileOrFolderByLink($fileSource, $itemType, $linkOwner, $isSharing) {
553 2
		if ($isSharing) {
554 2
			$actionSharer = 'shared_link_self';
555 2
			$actionOwner = 'reshared_link_by';
556
		} else if ($this->currentUser !== $linkOwner) {
557
			// Link expired
558
			$actionSharer = 'link_expired';
559
			$actionOwner = 'link_by_expired';
560
			$this->currentUser = $linkOwner;
561
			\OC::$server->getUserFolder($linkOwner);
562
		} else {
563
			$actionSharer = 'unshared_link_self';
564
			$actionOwner = 'unshared_link_by';
565
		}
566
567 2
		$this->view->chroot('/' . $this->currentUser . '/files');
568
569
		try {
570 2
			$path = $this->view->getPath($fileSource);
571 1
		} catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

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

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

Loading history...
572 1
			return;
573
		}
574
575 1
		$this->shareNotificationForOriginalOwners($this->currentUser, $actionOwner, '', $fileSource, $itemType);
0 ignored issues
show
Security Bug introduced by
It seems like $this->currentUser can also be of type false; however, OCA\Activity\FilesHooks:...tionForOriginalOwners() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
576
577 1
		$this->addNotificationsForUser(
578 1
			$this->currentUser, $actionSharer, [[$fileSource => $path]],
0 ignored issues
show
Security Bug introduced by
It seems like $this->currentUser can also be of type false; however, OCA\Activity\FilesHooks::addNotificationsForUser() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
579 1
			(int) $fileSource, $path, ($itemType === 'file'),
580 1
			$this->userSettings->getUserSetting($this->currentUser, 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
Security Bug introduced by
It seems like $this->currentUser can also be of type false; however, OCA\Activity\UserSettings::getUserSetting() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
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...
581 1
			$this->userSettings->getUserSetting($this->currentUser, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($this->currentUser, 'setting', 'batchtime') : 0
0 ignored issues
show
Security Bug introduced by
It seems like $this->currentUser can also be of type false; however, OCA\Activity\UserSettings::getUserSetting() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
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...
582
		);
583 1
	}
584
585
	/**
586
	 * Add notifications for the user that shares a file/folder
587
	 *
588
	 * @param string $subject
589
	 * @param string $shareWith
590
	 * @param int $fileSource
591
	 * @param string $itemType
592
	 */
593 2
	protected function shareNotificationForSharer($subject, $shareWith, $fileSource, $itemType) {
594 2
		$this->view->chroot('/' . $this->currentUser . '/files');
595
596
		try {
597 2
			$path = $this->view->getPath($fileSource);
598 1
		} catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

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

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

Loading history...
599 1
			return;
600
		}
601
602 1
		$this->addNotificationsForUser(
603 1
			$this->currentUser, $subject, [[$fileSource => $path], $shareWith],
0 ignored issues
show
Security Bug introduced by
It seems like $this->currentUser can also be of type false; however, OCA\Activity\FilesHooks::addNotificationsForUser() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
604 1
			$fileSource, $path, ($itemType === 'file'),
605 1
			$this->userSettings->getUserSetting($this->currentUser, 'stream', Files_Sharing::TYPE_SHARED),
0 ignored issues
show
Security Bug introduced by
It seems like $this->currentUser can also be of type false; however, OCA\Activity\UserSettings::getUserSetting() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
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...
606 1
			$this->userSettings->getUserSetting($this->currentUser, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($this->currentUser, 'setting', 'batchtime') : 0
0 ignored issues
show
Security Bug introduced by
It seems like $this->currentUser can also be of type false; however, OCA\Activity\UserSettings::getUserSetting() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
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...
607
		);
608 1
	}
609
610
	/**
611
	 * Add notifications for the user that shares a file/folder
612
	 *
613
	 * @param string $owner
614
	 * @param string $subject
615
	 * @param string $shareWith
616
	 * @param int $fileSource
617
	 * @param string $itemType
618
	 */
619 2
	protected function reshareNotificationForSharer($owner, $subject, $shareWith, $fileSource, $itemType) {
620 2
		$this->view->chroot('/' . $owner . '/files');
621
622
		try {
623 2
			$path = $this->view->getPath($fileSource);
624 1
		} catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

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

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

Loading history...
625 1
			return;
626
		}
627
628 1
		$this->addNotificationsForUser(
629 1
			$owner, $subject, [[$fileSource => $path], $this->currentUser, $shareWith],
630 1
			$fileSource, $path, ($itemType === 'file'),
631 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...
632 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...
633
		);
634 1
	}
635
636
	/**
637
	 * Add notifications for the owners whose files have been reshared
638
	 *
639
	 * @param string $currentOwner
640
	 * @param string $subject
641
	 * @param string $shareWith
642
	 * @param int $fileSource
643
	 * @param string $itemType
644
	 */
645 10
	protected function shareNotificationForOriginalOwners($currentOwner, $subject, $shareWith, $fileSource, $itemType) {
646
		// Get the full path of the current user
647 10
		$this->view->chroot('/' . $currentOwner . '/files');
648
649
		try {
650 10
			$path = $this->view->getPath($fileSource);
651 1
		} catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

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

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

Loading history...
652 1
			return;
653
		}
654
655
		/**
656
		 * Get the original owner and his path
657
		 */
658 9
		$owner = $this->view->getOwner($path);
659 9
		if ($owner !== $currentOwner) {
660 7
			$this->reshareNotificationForSharer($owner, $subject, $shareWith, $fileSource, $itemType);
661
		}
662
663
		/**
664
		 * Get the sharee who shared the item with the currentUser
665
		 */
666 9
		$this->view->chroot('/' . $currentOwner . '/files');
667 9
		$mount = $this->view->getMount($path);
668 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...
669 1
			return;
670
		}
671
672 8
		$storage = $mount->getStorage();
673 8
		if (!$storage->instanceOfStorage('OC\Files\Storage\Shared')) {
674 1
			return;
675
		}
676
677
		/** @var \OC\Files\Storage\Shared $storage */
678 7
		$shareOwner = $storage->getSharedFrom();
679 7
		if ($shareOwner === '' || $shareOwner === null || $shareOwner === $owner || $shareOwner === $currentOwner) {
680 5
			return;
681
		}
682
683 2
		$this->reshareNotificationForSharer($shareOwner, $subject, $shareWith, $fileSource, $itemType);
684 2
	}
685
686
	/**
687
	 * Adds the activity and email for a user when the settings require it
688
	 *
689
	 * @param string $user
690
	 * @param string $subject
691
	 * @param array $subjectParams
692
	 * @param int $fileId
693
	 * @param string $path
694
	 * @param bool $isFile If the item is a file, we link to the parent directory
695
	 * @param bool $streamSetting
696
	 * @param int $emailSetting
697
	 * @param string $type
698
	 */
699 11
	protected function addNotificationsForUser($user, $subject, $subjectParams, $fileId, $path, $isFile, $streamSetting, $emailSetting, $type = Files_Sharing::TYPE_SHARED) {
700 11
		if (!$streamSetting && !$emailSetting) {
701 1
			return;
702
		}
703
704 10
		$selfAction = $user === $this->currentUser;
705 10
		$app = $type === Files_Sharing::TYPE_SHARED ? 'files_sharing' : 'files';
706 10
		$link = $this->urlGenerator->linkToRouteAbsolute('files.view.index', array(
707 10
			'dir' => ($isFile) ? dirname($path) : $path,
708
		));
709
710 10
		$objectType = ($fileId) ? 'files' : '';
711
712 10
		$event = $this->manager->generateEvent();
713 10
		$event->setApp($app)
714 10
			->setType($type)
715 10
			->setAffectedUser($user)
716 10
			->setAuthor($this->currentUser)
717 10
			->setTimestamp(time())
718 10
			->setSubject($subject, $subjectParams)
719 10
			->setObject($objectType, $fileId, $path)
720 10
			->setLink($link);
721
722
		// Add activity to stream
723 10
		if ($streamSetting && (!$selfAction || $this->userSettings->getUserSetting($this->currentUser, 'setting', 'self'))) {
724 3
			$this->activityData->send($event);
725
		}
726
727
		// Add activity to mail queue
728 10
		if ($emailSetting && (!$selfAction || $this->userSettings->getUserSetting($this->currentUser, 'setting', 'selfemail'))) {
729 5
			$latestSend = time() + $emailSetting;
730 5
			$this->activityData->storeMail($event, $latestSend);
731
		}
732 10
	}
733
}
734