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
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); |
|
} |
|
} |
|
}
|
|
|