You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1170 lines
36 KiB

<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Frank Karlitschek <frank@karlitschek.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Activity;
use OC\Files\Filesystem;
use OC\Files\View;
use OCA\Activity\BackgroundJob\RemoteActivity;
use OCA\Activity\Extension\Files;
use OCA\Activity\Extension\Files_Sharing;
use OCP\Activity\IManager;
use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\ILogger;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Share;
use OCP\Share\IShare;
use OCP\Share\IShareHelper;
/**
* The class to handle the filesystem hooks
*/
class FilesHooks {
const USER_BATCH_SIZE = 50;
/** @var \OCP\Activity\IManager */
protected $manager;
/** @var \OCA\Activity\Data */
protected $activityData;
/** @var \OCA\Activity\UserSettings */
protected $userSettings;
/** @var \OCP\IGroupManager */
protected $groupManager;
/** @var \OCP\IDBConnection */
protected $connection;
/** @var \OC\Files\View */
protected $view;
/** @var IRootFolder */
protected $rootFolder;
/** @var IShareHelper */
protected $shareHelper;
/** @var IURLGenerator */
protected $urlGenerator;
/** @var ILogger */
protected $logger;
/** @var CurrentUser */
protected $currentUser;
/** @var string|bool */
protected $moveCase = false;
/** @var array */
protected $oldAccessList;
/** @var string */
protected $oldParentPath;
/** @var string */
protected $oldParentOwner;
/** @var string */
protected $oldParentId;
/**
* Constructor
*
* @param IManager $manager
* @param Data $activityData
* @param UserSettings $userSettings
* @param IGroupManager $groupManager
* @param View $view
* @param IRootFolder $rootFolder
* @param IShareHelper $shareHelper
* @param IDBConnection $connection
* @param IURLGenerator $urlGenerator
* @param ILogger $logger
* @param CurrentUser $currentUser
*/
public function __construct(IManager $manager,
Data $activityData,
UserSettings $userSettings,
IGroupManager $groupManager,
View $view,
IRootFolder $rootFolder,
IShareHelper $shareHelper,
IDBConnection $connection,
IURLGenerator $urlGenerator,
ILogger $logger,
CurrentUser $currentUser) {
$this->manager = $manager;
$this->activityData = $activityData;
$this->userSettings = $userSettings;
$this->groupManager = $groupManager;
$this->view = $view;
$this->rootFolder = $rootFolder;
$this->shareHelper = $shareHelper;
$this->connection = $connection;
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->currentUser = $currentUser;
}
/**
* Store the create hook events
* @param string $path Path of the file that has been created
*/
public function fileCreate($path) {
if ($path === '/' || $path === '' || $path === null) {
return;
}
if ($this->currentUser->getUserIdentifier() !== '') {
$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CREATED, 'created_self', 'created_by');
} else {
$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CREATED, '', 'created_public');
}
}
/**
* Store the update hook events
* @param string $path Path of the file that has been modified
*/
public function fileUpdate($path) {
$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CHANGED, 'changed_self', 'changed_by');
}
/**
* Store the delete hook events
* @param string $path Path of the file that has been deleted
*/
public function fileDelete($path) {
$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_DELETED, 'deleted_self', 'deleted_by');
}
/**
* Store the restore hook events
* @param string $path Path of the file that has been restored
*/
public function fileRestore($path) {
$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_RESTORED, 'restored_self', 'restored_by');
}
/**
* Creates the entries for file actions on $file_path
*
* @param string $filePath The file that is being changed
* @param int $activityType The activity type
* @param string $subject The subject for the actor
* @param string $subjectBy The subject for other users (with "by $actor")
*/
protected function addNotificationsForFileAction($filePath, $activityType, $subject, $subjectBy) {
// Do not add activities for .part-files
if (substr($filePath, -5) === '.part') {
return;
}
list($filePath, $uidOwner, $fileId) = $this->getSourcePathAndOwner($filePath);
if ($fileId === 0) {
// Could not find the file for the owner ...
return;
}
$accessList = $this->getUserPathsFromPath($filePath, $uidOwner);
$this->generateRemoteActivity($accessList['remotes'], $activityType, time(), $this->currentUser->getCloudId(), $accessList['ownerPath']);
$affectedUsers = $accessList['users'];
$filteredStreamUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'stream', $activityType);
$filteredEmailUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'email', $activityType);
foreach ($affectedUsers as $user => $path) {
$user = (string) $user;
if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
continue;
}
if ($user === $this->currentUser->getUID()) {
$userSubject = $subject;
$userParams = [[$fileId => $path]];
} else {
$userSubject = $subjectBy;
$userParams = [[$fileId => $path], $this->currentUser->getUserIdentifier()];
}
$this->addNotificationsForUser(
$user, $userSubject, $userParams,
$fileId, $path, true,
!empty($filteredStreamUsers[$user]),
$filteredEmailUsers[$user] ?? false,
$activityType
);
}
}
protected function generateRemoteActivity(array $remoteUsers, $type, $time, $actor, $ownerPath = false) {
foreach ($remoteUsers as $remoteUser => $info) {
if ($actor === $remoteUser) {
// Current user receives the notification on their own instance already
continue;
}
$arguments = [
$remoteUser,
$info['token'],
$ownerPath !== false ? substr($ownerPath, strlen($info['node_path'])) : $info['node_path'],
$type,
$time,
$actor,
];
if (isset($info['second_path'])) {
$arguments[] = $info['second_path'];
}
\OC::$server->getJobList()->add(RemoteActivity::class, $arguments);
}
}
/**
* Collect some information for move/renames
*
* @param string $oldPath Path of the file that has been moved
* @param string $newPath Path of the file that has been moved
*/
public function fileMove($oldPath, $newPath) {
if (substr($oldPath, -5) === '.part' || substr($newPath, -5) === '.part') {
// Do not add activities for .part-files
$this->moveCase = false;
return;
}
$oldDir = dirname($oldPath);
$newDir = dirname($newPath);
if ($oldDir === $newDir) {
/**
* a/b moved to a/c
*
* Cases:
* - a/b shared: no visible change
* - a/ shared: rename
*/
$this->moveCase = 'rename';
return;
}
if (strpos($oldDir, $newDir) === 0) {
/**
* a/b/c moved to a/c
*
* Cases:
* - a/b/c shared: no visible change
* - a/b/ shared: delete
* - a/ shared: move/rename
*/
$this->moveCase = 'moveUp';
} else if (strpos($newDir, $oldDir) === 0) {
/**
* a/b moved to a/c/b
*
* Cases:
* - a/b shared: no visible change
* - a/c/ shared: add
* - a/ shared: move/rename
*/
$this->moveCase = 'moveDown';
} else {
/**
* a/b/c moved to a/d/c
*
* Cases:
* - a/b/c shared: no visible change
* - a/b/ shared: delete
* - a/d/ shared: add
* - a/ shared: move/rename
*/
$this->moveCase = 'moveCross';
}
list($this->oldParentPath, $this->oldParentOwner, $this->oldParentId) = $this->getSourcePathAndOwner($oldDir);
if ($this->oldParentId === 0) {
// Could not find the file for the owner ...
$this->moveCase = false;
return;
}
$this->oldAccessList = $this->getUserPathsFromPath($this->oldParentPath, $this->oldParentOwner);
}
/**
* Store the move hook events
*
* @param string $oldPath Path of the file that has been moved
* @param string $newPath Path of the file that has been moved
*/
public function fileMovePost($oldPath, $newPath) {
// Do not add activities for .part-files
if ($this->moveCase === false) {
return;
}
switch ($this->moveCase) {
case 'rename':
$this->fileRenaming($oldPath, $newPath);
break;
case 'moveUp':
case 'moveDown':
case 'moveCross':
$this->fileMoving($oldPath, $newPath);
break;
}
$this->moveCase = false;
}
/**
* Renaming a file inside the same folder (a/b to a/c)
*
* @param string $oldPath
* @param string $newPath
*/
protected function fileRenaming($oldPath, $newPath) {
$dirName = dirname($newPath);
$fileName = basename($newPath);
$oldFileName = basename($oldPath);
list(, , $fileId) = $this->getSourcePathAndOwner($newPath);
list($parentPath, $parentOwner, $parentId) = $this->getSourcePathAndOwner($dirName);
if ($fileId === 0 || $parentId === 0) {
// Could not find the file for the owner ...
return;
}
$accessList = $this->getUserPathsFromPath($parentPath, $parentOwner);
$renameRemotes = [];
foreach ($accessList['remotes'] as $remote => $info) {
$renameRemotes[$remote] = [
'token' => $info['token'],
'node_path' => substr($newPath, strlen($info['node_path'])),
'second_path' => substr($oldPath, strlen($info['node_path'])),
];
}
$this->generateRemoteActivity($renameRemotes, Files::TYPE_SHARE_CHANGED, time(), $this->currentUser->getCloudId());
$affectedUsers = $accessList['users'];
$filteredStreamUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'stream', Files::TYPE_SHARE_CHANGED);
$filteredEmailUsers = $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'email', Files::TYPE_SHARE_CHANGED);
foreach ($affectedUsers as $user => $path) {
if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
continue;
}
if ($user === $this->currentUser->getUID()) {
$userSubject = 'renamed_self';
$userParams = [
[$fileId => $path . '/' . $fileName],
[$fileId => $path . '/' . $oldFileName],
];
} else {
$userSubject = 'renamed_by';
$userParams = [
[$fileId => $path . '/' . $fileName],
$this->currentUser->getUserIdentifier(),
[$fileId => $path . '/' . $oldFileName],
];
}
$this->addNotificationsForUser(
$user, $userSubject, $userParams,
$fileId, $path . '/' . $fileName, true,
!empty($filteredStreamUsers[$user]),
$filteredEmailUsers[$user] ?? false,
Files::TYPE_SHARE_CHANGED
);
}
}
/**
* Moving a file from one folder to another
*
* @param string $oldPath
* @param string $newPath
*/
protected function fileMoving($oldPath, $newPath) {
$dirName = dirname($newPath);
$fileName = basename($newPath);
$oldFileName = basename($oldPath);
list(, , $fileId) = $this->getSourcePathAndOwner($newPath);
list($parentPath, $parentOwner, $parentId) = $this->getSourcePathAndOwner($dirName);
if ($fileId === 0 || $parentId === 0) {
// Could not find the file for the owner ...
return;
}
$accessList = $this->getUserPathsFromPath($parentPath, $parentOwner);
$affectedUsers = $accessList['users'];
$oldUsers = $this->oldAccessList['users'];
$beforeUsers = array_keys($oldUsers);
$afterUsers = array_keys($affectedUsers);
$deleteUsers = array_diff($beforeUsers, $afterUsers);
$this->generateDeleteActivities($deleteUsers, $oldUsers, $fileId, $oldFileName);
$addUsers = array_diff($afterUsers, $beforeUsers);
$this->generateAddActivities($addUsers, $affectedUsers, $fileId, $fileName);
$moveUsers = array_intersect($beforeUsers, $afterUsers);
$this->generateMoveActivities($moveUsers, $oldUsers, $affectedUsers, $fileId, $oldFileName, $parentId, $fileName);
$beforeRemotes = $this->oldAccessList['remotes'];
$afterRemotes = $accessList['remotes'];
$addRemotes = $deleteRemotes = $moveRemotes = [];
foreach ($afterRemotes as $remote => $info) {
if (isset($beforeRemotes[$remote])) {
// Move
$info['node_path'] = substr($newPath, strlen($info['node_path']));
$info['second_path'] = substr($oldPath, strlen($beforeRemotes[$remote]['node_path']));
$moveRemotes[$remote] = $info;
} else {
$info['node_path'] = substr($newPath, strlen($info['node_path']));
$addRemotes[$remote] = $info;
}
}
foreach ($beforeRemotes as $remote => $info) {
if (!isset($afterRemotes[$remote])) {
$info['node_path'] = substr($oldPath, strlen($info['node_path']));
$deleteRemotes[$remote] = $info;
}
}
$this->generateRemoteActivity($deleteRemotes, Files::TYPE_SHARE_DELETED, time(), $this->currentUser->getCloudId());
$this->generateRemoteActivity($addRemotes, Files::TYPE_SHARE_CREATED, time(), $this->currentUser->getCloudId());
$this->generateRemoteActivity($moveRemotes, Files::TYPE_SHARE_CHANGED, time(), $this->currentUser->getCloudId());
}
/**
* @param string[] $users
* @param string[] $pathMap
* @param int $fileId
* @param string $oldFileName
*/
protected function generateDeleteActivities($users, $pathMap, $fileId, $oldFileName) {
if (empty($users)) {
return;
}
$filteredStreamUsers = $this->userSettings->filterUsersBySetting($users, 'stream', Files::TYPE_SHARE_DELETED);
$filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', Files::TYPE_SHARE_DELETED);
foreach ($users as $user) {
if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
continue;
}
$path = $pathMap[$user];
if ($user === $this->currentUser->getUID()) {
$userSubject = 'deleted_self';
$userParams = [[$fileId => $path . '/' . $oldFileName]];
} else {
$userSubject = 'deleted_by';
$userParams = [[$fileId => $path . '/' . $oldFileName], $this->currentUser->getUserIdentifier()];
}
$this->addNotificationsForUser(
$user, $userSubject, $userParams,
$fileId, $path . '/' . $oldFileName, true,
!empty($filteredStreamUsers[$user]),
$filteredEmailUsers[$user] ?? false,
Files::TYPE_SHARE_DELETED
);
}
}
/**
* @param string[] $users
* @param string[] $pathMap
* @param int $fileId
* @param string $fileName
*/
protected function generateAddActivities($users, $pathMap, $fileId, $fileName) {
if (empty($users)) {
return;
}
$filteredStreamUsers = $this->userSettings->filterUsersBySetting($users, 'stream', Files::TYPE_SHARE_CREATED);
$filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', Files::TYPE_SHARE_CREATED);
foreach ($users as $user) {
if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
continue;
}
$path = $pathMap[$user];
if ($user === $this->currentUser->getUID()) {
$userSubject = 'created_self';
$userParams = [[$fileId => $path . '/' . $fileName]];
} else {
$userSubject = 'created_by';
$userParams = [[$fileId => $path . '/' . $fileName], $this->currentUser->getUserIdentifier()];
}
$this->addNotificationsForUser(
$user, $userSubject, $userParams,
$fileId, $path . '/' . $fileName, true,
!empty($filteredStreamUsers[$user]),
$filteredEmailUsers[$user] ?? false,
Files::TYPE_SHARE_CREATED
);
}
}
/**
* @param string[] $users
* @param string[] $beforePathMap
* @param string[] $afterPathMap
* @param int $fileId
* @param string $oldFileName
* @param int $newParentId
* @param string $fileName
*/
protected function generateMoveActivities($users, $beforePathMap, $afterPathMap, $fileId, $oldFileName, $newParentId, $fileName) {
if (empty($users)) {
return;
}
$filteredStreamUsers = $this->userSettings->filterUsersBySetting($users, 'stream', Files::TYPE_SHARE_CHANGED);
$filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', Files::TYPE_SHARE_CHANGED);
foreach ($users as $user) {
if (empty($filteredStreamUsers[$user]) && empty($filteredEmailUsers[$user])) {
continue;
}
if ($oldFileName === $fileName) {
$userParams = [[$newParentId => $afterPathMap[$user] . '/']];
} else {
$userParams = [[$fileId => $afterPathMap[$user] . '/' . $fileName]];
}
if ($user === $this->currentUser->getUID()) {
$userSubject = 'moved_self';
} else {
$userSubject = 'moved_by';
$userParams[] = $this->currentUser->getUserIdentifier();
}
$userParams[] = [$fileId => $beforePathMap[$user] . '/' . $oldFileName];
$this->addNotificationsForUser(
$user, $userSubject, $userParams,
$fileId, $afterPathMap[$user] . '/' . $fileName, true,
!empty($filteredStreamUsers[$user]),
$filteredEmailUsers[$user] ?? false,
Files::TYPE_SHARE_CHANGED
);
}
}
/**
* Returns a "username => path" map for all affected users
*
* @param string $path
* @param string $uidOwner
* @return array
*/
protected function getUserPathsFromPath($path, $uidOwner) {
try {
$node = $this->rootFolder->getUserFolder($uidOwner)->get($path);
} catch (NotFoundException $e) {
return [];
}
if (!$node instanceof Node) {
return [];
}
$accessList = $this->shareHelper->getPathsForAccessList($node);
$path = $node->getPath();
$sections = explode('/', $path, 4);
$accessList['ownerPath'] = '/';
if (isset($sections[3])) {
// Not the case when a file in root is renamed
$accessList['ownerPath'] .= $sections[3];
}
return $accessList;
}
/**
* Return the source
*
* @param string $path
* @return array
*/
protected function getSourcePathAndOwner($path) {
$view = Filesystem::getView();
$owner = $view->getOwner($path);
$owner = !is_string($owner) || $owner === '' ? null : $owner;
$fileId = 0;
$currentUser = $this->currentUser->getUID();
if ($owner === null || $owner !== $currentUser) {
/** @var \OCP\Files\Storage\IStorage $storage */
list($storage,) = $view->resolvePath($path);
if ($owner !== null && !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
Filesystem::initMountPoints($owner);
} else {
// Probably a remote user, let's try to at least generate activities
// for the current user
if ($currentUser === null) {
list(,$owner,) = explode('/', $view->getAbsolutePath($path), 3);
} else {
$owner = $currentUser;
}
}
}
$info = Filesystem::getFileInfo($path);
if ($info !== false) {
$ownerView = new View('/' . $owner . '/files');
$fileId = (int) $info['fileid'];
$path = $ownerView->getPath($fileId);
}
return array($path, $owner, $fileId);
}
/**
* Manage sharing events
* @param array $params The hook params
*/
public function share($params) {
if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
if ((int) $params['shareType'] === Share::SHARE_TYPE_USER) {
$this->shareWithUser($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget']);
} else if ((int) $params['shareType'] === Share::SHARE_TYPE_GROUP) {
$this->shareWithGroup($params['shareWith'], (int) $params['fileSource'], $params['itemType'], $params['fileTarget'], (int) $params['id']);
} else if ((int) $params['shareType'] === Share::SHARE_TYPE_LINK) {
$this->shareByLink((int) $params['fileSource'], $params['itemType'], $params['uidOwner']);
}
}
}
/**
* Sharing a file or folder with a user
*
* @param string $shareWith
* @param int $fileSource File ID that is being shared
* @param string $itemType File type that is being shared (file or folder)
* @param string $fileTarget File path
*/
protected function shareWithUser($shareWith, $fileSource, $itemType, $fileTarget) {
// User performing the share
$this->shareNotificationForSharer('shared_user_self', $shareWith, $fileSource, $itemType);
if ($this->currentUser->getUID() !== null) {
$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), 'reshared_user_by', $shareWith, $fileSource, $itemType);
}
// New shared user
$this->addNotificationsForUser(
$shareWith, 'shared_with_by', [[$fileSource => $fileTarget], $this->currentUser->getUserIdentifier()],
(int) $fileSource, $fileTarget, $itemType === 'file',
$this->userSettings->getUserSetting($shareWith, 'stream', Files_Sharing::TYPE_SHARED),
$this->userSettings->getUserSetting($shareWith, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($shareWith, 'setting', 'batchtime') : false
);
}
/**
* Sharing a file or folder with a group
*
* @param string $shareWith
* @param int $fileSource File ID that is being shared
* @param string $itemType File type that is being shared (file or folder)
* @param string $fileTarget File path
* @param int $shareId The Share ID of this share
*/
protected function shareWithGroup($shareWith, $fileSource, $itemType, $fileTarget, $shareId) {
// Members of the new group
$group = $this->groupManager->get($shareWith);
if (!($group instanceof IGroup)) {
return;
}
// User performing the share
$this->shareNotificationForSharer('shared_group_self', $shareWith, $fileSource, $itemType);
if ($this->currentUser->getUID() !== null) {
$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), 'reshared_group_by', $shareWith, $fileSource, $itemType);
}
$offset = 0;
$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
while (!empty($users)) {
$this->addNotificationsForGroupUsers($users, 'shared_with_by', $fileSource, $itemType, $fileTarget, $shareId);
$offset += self::USER_BATCH_SIZE;
$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
}
}
/**
* Sharing a file or folder via link/public
*
* @param int $fileSource File ID that is being shared
* @param string $itemType File type that is being shared (file or folder)
* @param string $linkOwner
*/
protected function shareByLink($fileSource, $itemType, $linkOwner) {
$this->view->chroot('/' . $linkOwner . '/files');
try {
$path = $this->view->getPath($fileSource);
} catch (NotFoundException $e) {
return;
}
$this->shareNotificationForOriginalOwners($linkOwner, 'reshared_link_by', '', $fileSource, $itemType);
$this->addNotificationsForUser(
$linkOwner, 'shared_link_self', [[$fileSource => $path]],
(int) $fileSource, $path, $itemType === 'file',
$this->userSettings->getUserSetting($linkOwner, 'stream', Files_Sharing::TYPE_SHARED),
$this->userSettings->getUserSetting($linkOwner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($linkOwner, 'setting', 'batchtime') : false
);
}
/**
* Manage unsharing events
* @param IShare $share
* @throws \OCP\Files\NotFoundException
*/
public function unShare(IShare $share) {
if (in_array($share->getNodeType(), ['file', 'folder'], true)) {
if ($share->getShareType() === Share::SHARE_TYPE_USER) {
$this->unshareFromUser($share);
} else if ($share->getShareType() === Share::SHARE_TYPE_GROUP) {
$this->unshareFromGroup($share);
} else if ($share->getShareType() === Share::SHARE_TYPE_LINK) {
$this->unshareLink($share);
}
}
}
/**
* Manage unsharing a share from self only events
* @param IShare $share
* @throws \OCP\Files\NotFoundException
*/
public function unShareSelf(IShare $share) {
if (in_array($share->getNodeType(), ['file', 'folder'], true)) {
if ($share->getShareType() === Share::SHARE_TYPE_GROUP) {
$this->unshareFromSelfGroup($share);
} else {
$this->unShare($share);
}
}
}
/**
* Unharing a file or folder from a user
*
* @param IShare $share
* @throws \OCP\Files\NotFoundException
*/
protected function unshareFromUser(IShare $share) {
if ($share->getSharedWith() === $this->currentUser->getUID()) {
$this->selfUnshareFromUser($share);
return;
}
if ($share->isExpired()) {
$actionSharer = 'expired_user';
$actionOwner = 'expired_user';
$actionUser = 'expired';
} else {
$actionSharer = 'unshared_user_self';
$actionOwner = 'unshared_user_by';
$actionUser = 'unshared_by';
}
// User performing the share
$this->shareNotificationForSharer($actionSharer, $share->getSharedWith(), $share->getNodeId(), $share->getNodeType());
// Owner
if ($this->currentUser->getUID() !== null) {
$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), $actionOwner, $share->getSharedWith(), $share->getNodeId(), $share->getNodeType());
}
// Recipient
$this->addNotificationsForUser(
$share->getSharedWith(), $actionUser, [[$share->getNodeId() => $share->getTarget()], $this->currentUser->getUserIdentifier()],
$share->getNodeId(), $share->getTarget(), $share->getNodeType() === 'file',
$this->userSettings->getUserSetting($share->getSharedWith(), 'stream', Files_Sharing::TYPE_SHARED),
$this->userSettings->getUserSetting($share->getSharedWith(), 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($share->getSharedWith(), 'setting', 'batchtime') : false
);
}
/**
* Unharing a file or folder from a user
*
* @param IShare $share
* @throws \OCP\Files\NotFoundException
*/
protected function selfUnshareFromUser(IShare $share) {
// User performing the share
$this->shareNotificationForSharer('self_unshared', $share->getSharedWith(), $share->getNodeId(), $share->getNodeType());
// Owner
if ($this->currentUser->getUID() !== null) {
$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), 'self_unshared_by', $share->getSharedWith(), $share->getNodeId(), $share->getNodeType());
}
}
/**
* Unsharing a file or folder from a group
*
* @param IShare $share
* @throws \OCP\Files\NotFoundException
*/
protected function unshareFromGroup(IShare $share) {
// Members of the new group
$group = $this->groupManager->get($share->getSharedWith());
if (!($group instanceof IGroup)) {
return;
}
if ($share->isExpired()) {
$actionSharer = 'expired_group';
$actionOwner = 'expired_group';
$actionUser = 'expired';
} else {
$actionSharer = 'unshared_group_self';
$actionOwner = 'unshared_group_by';
$actionUser = 'unshared_by';
}
// User performing the share
$this->shareNotificationForSharer($actionSharer, $share->getSharedWith(), $share->getNodeId(), $share->getNodeType());
if ($this->currentUser->getUID() !== null) {
$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), $actionOwner, $share->getSharedWith(), $share->getNodeId(), $share->getNodeType());
}
$offset = 0;
$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
while (!empty($users)) {
$this->addNotificationsForGroupUsers($users, $actionUser, $share->getNodeId(), $share->getNodeType(), $share->getTarget(), $share->getId());
$offset += self::USER_BATCH_SIZE;
$users = $group->searchUsers('', self::USER_BATCH_SIZE, $offset);
}
}
/**
* Unsharing a file or folder from self from a group share
*
* @param IShare $share
* @throws \OCP\Files\NotFoundException
*/
protected function unshareFromSelfGroup(IShare $share) {
// User performing the unshare
$this->shareNotificationForSharer('self_unshared', $this->currentUser->getUID(), $share->getNodeId(), $share->getNodeType());
// Owner
if ($this->currentUser->getUID() !== null) {
$this->shareNotificationForOriginalOwners($this->currentUser->getUID(), 'self_unshared_by', $this->currentUser->getUID(), $share->getNodeId(), $share->getNodeType());
}
}
/**
* Sharing a file or folder via link/public
*
* @param IShare $share
* @throws \OCP\Files\NotFoundException
*/
protected function unshareLink(IShare $share) {
$owner = $share->getSharedBy();
if ($share->isExpired()) {
// Link expired
$actionSharer = 'link_expired';
$actionOwner = 'link_by_expired';
} else {
$actionSharer = 'unshared_link_self';
$actionOwner = 'unshared_link_by';
}
$this->addNotificationsForUser(
$owner, $actionSharer, [[$share->getNodeId() => $share->getTarget()]],
$share->getNodeId(), $share->getTarget(), $share->getNodeType() === 'file',
$this->userSettings->getUserSetting($owner, 'stream', Files_Sharing::TYPE_SHARED),
$this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : false
);
if ($share->getSharedBy() !== $share->getShareOwner()) {
$owner = $share->getShareOwner();
$this->addNotificationsForUser(
$owner, $actionOwner, [[$share->getNodeId() => $share->getTarget()], $share->getSharedBy()],
$share->getNodeId(), $share->getTarget(), $share->getNodeType() === 'file',
$this->userSettings->getUserSetting($owner, 'stream', Files_Sharing::TYPE_SHARED),
$this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : false
);
}
}
/**
* @param IUser[] $usersInGroup
* @param string $actionUser
* @param int $fileSource File ID that is being shared
* @param string $itemType File type that is being shared (file or folder)
* @param string $fileTarget File path
* @param int $shareId The Share ID of this share
*/
protected function addNotificationsForGroupUsers(array $usersInGroup, $actionUser, $fileSource, $itemType, $fileTarget, $shareId) {
$affectedUsers = [];
foreach ($usersInGroup as $user) {
$affectedUsers[$user->getUID()] = $fileTarget;
}
// Remove the triggering user, we already managed his notifications
unset($affectedUsers[$this->currentUser->getUID()]);
if (empty($affectedUsers)) {
return;
}
$userIds = array_keys($affectedUsers);
$filteredStreamUsersInGroup = $this->userSettings->filterUsersBySetting($userIds, 'stream', Files_Sharing::TYPE_SHARED);
$filteredEmailUsersInGroup = $this->userSettings->filterUsersBySetting($userIds, 'email', Files_Sharing::TYPE_SHARED);
$affectedUsers = $this->fixPathsForShareExceptions($affectedUsers, $shareId);
foreach ($affectedUsers as $user => $path) {
if (empty($filteredStreamUsersInGroup[$user]) && empty($filteredEmailUsersInGroup[$user])) {
continue;
}
$this->addNotificationsForUser(
$user, $actionUser, [[$fileSource => $path], $this->currentUser->getUserIdentifier()],
$fileSource, $path, ($itemType === 'file'),
!empty($filteredStreamUsersInGroup[$user]),
$filteredEmailUsersInGroup[$user] ?? false
);
}
}
/**
* Check when there was a naming conflict and the target is different
* for some of the users
*
* @param array $affectedUsers
* @param int $shareId
* @return mixed
*/
protected function fixPathsForShareExceptions(array $affectedUsers, $shareId) {
$queryBuilder = $this->connection->getQueryBuilder();
$queryBuilder->select(['share_with', 'file_target'])
->from('share')
->where($queryBuilder->expr()->eq('parent', $queryBuilder->createParameter('parent')))
->setParameter('parent', (int) $shareId);
$query = $queryBuilder->execute();
while ($row = $query->fetch()) {
$affectedUsers[$row['share_with']] = $row['file_target'];
}
return $affectedUsers;
}
/**
* Add notifications for the user that shares a file/folder
*
* @param string $subject
* @param string $shareWith
* @param int $fileSource
* @param string $itemType
*/
protected function shareNotificationForSharer($subject, $shareWith, $fileSource, $itemType) {
$sharer = $this->currentUser->getUID();
if ($sharer === null) {
return;
}
$this->view->chroot('/' . $sharer . '/files');
try {
$path = $this->view->getPath($fileSource);
} catch (NotFoundException $e) {
return;
}
$this->addNotificationsForUser(
$sharer, $subject, [[$fileSource => $path], $shareWith],
$fileSource, $path, ($itemType === 'file'),
$this->userSettings->getUserSetting($sharer, 'stream', Files_Sharing::TYPE_SHARED),
$this->userSettings->getUserSetting($sharer, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($sharer, 'setting', 'batchtime') : false
);
}
/**
* Add notifications for the user that shares a file/folder
*
* @param string $owner
* @param string $subject
* @param string $shareWith
* @param int $fileSource
* @param string $itemType
*/
protected function reshareNotificationForSharer($owner, $subject, $shareWith, $fileSource, $itemType) {
$this->view->chroot('/' . $owner . '/files');
try {
$path = $this->view->getPath($fileSource);
} catch (NotFoundException $e) {
return;
}
$this->addNotificationsForUser(
$owner, $subject, [[$fileSource => $path], $this->currentUser->getUserIdentifier(), $shareWith],
$fileSource, $path, ($itemType === 'file'),
$this->userSettings->getUserSetting($owner, 'stream', Files_Sharing::TYPE_SHARED),
$this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : false
);
}
/**
* Add notifications for the owners whose files have been reshared
*
* @param string $currentOwner
* @param string $subject
* @param string $shareWith
* @param int $fileSource
* @param string $itemType
*/
protected function shareNotificationForOriginalOwners($currentOwner, $subject, $shareWith, $fileSource, $itemType) {
// Get the full path of the current user
$this->view->chroot('/' . $currentOwner . '/files');
try {
$path = $this->view->getPath($fileSource);
} catch (NotFoundException $e) {
return;
}
/**
* Get the original owner and his path
*/
$owner = $this->view->getOwner($path);
if ($owner !== $currentOwner) {
$this->reshareNotificationForSharer($owner, $subject, $shareWith, $fileSource, $itemType);
}
/**
* Get the sharee who shared the item with the currentUser
*/
$this->view->chroot('/' . $currentOwner . '/files');
$mount = $this->view->getMount($path);
if (!($mount instanceof IMountPoint)) {
return;
}
$storage = $mount->getStorage();
if (!$storage->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) {
return;
}
/** @var \OCA\Files_Sharing\SharedStorage $storage */
$shareOwner = $storage->getSharedFrom();
if ($shareOwner === '' || $shareOwner === null || $shareOwner === $owner || $shareOwner === $currentOwner) {
return;
}
$this->reshareNotificationForSharer($shareOwner, $subject, $shareWith, $fileSource, $itemType);
}
/**
* Adds the activity and email for a user when the settings require it
*
* @param string $user
* @param string $subject
* @param array $subjectParams
* @param int $fileId
* @param string $path
* @param bool $isFile If the item is a file, we link to the parent directory
* @param bool $streamSetting
* @param int $emailSetting
* @param string $type
*/
protected function addNotificationsForUser($user, $subject, $subjectParams, $fileId, $path, $isFile, $streamSetting, $emailSetting, $type = Files_Sharing::TYPE_SHARED) {
if (!$streamSetting && !$emailSetting) {
return;
}
$user = (string)$user;
$selfAction = $user === $this->currentUser->getUID();
$app = $type === Files_Sharing::TYPE_SHARED ? 'files_sharing' : 'files';
$link = $this->urlGenerator->linkToRouteAbsolute('files.view.index', array(
'dir' => ($isFile) ? dirname($path) : $path,
));
$objectType = ($fileId) ? 'files' : '';
$event = $this->manager->generateEvent();
try {
$event->setApp($app)
->setType($type)
->setAffectedUser($user)
->setTimestamp(time())
->setSubject($subject, $subjectParams)
->setObject($objectType, $fileId, $path)
->setLink($link);
if ($this->currentUser->getUID() !== null) {
// Allow this to be empty for guests
$event->setAuthor($this->currentUser->getUID());
}
} catch (\InvalidArgumentException $e) {
$this->logger->logException($e);
}
// Add activity to stream
if ($streamSetting && (!$selfAction || $this->userSettings->getUserSetting($this->currentUser->getUID(), 'setting', 'self'))) {
$this->activityData->send($event);
}
// Add activity to mail queue
if ($emailSetting !== false && (!$selfAction || $this->userSettings->getUserSetting($this->currentUser->getUID(), 'setting', 'selfemail'))) {
$latestSend = time() + $emailSetting;
$this->activityData->storeMail($event, $latestSend);
}
}
}