/home/ivoiecob/email.hirewise-va.com/modules/SharedFiles/Module.php
<?php
/**
* This code is licensed under AGPLv3 license or Afterlogic Software License
* if commercial version of the product was purchased.
* For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
*/
namespace Aurora\Modules\SharedFiles;
use Afterlogic\DAV\Constants;
use Afterlogic\DAV\FS\Directory as Directory;
use Afterlogic\DAV\FS\File;
use Afterlogic\DAV\FS\Permission;
use Afterlogic\DAV\Server;
use Aurora\Api;
use Aurora\Modules\Core\Module as CoreModule;
use Aurora\Modules\Files\Module as FilesModule;
use Aurora\Modules\SharedFiles\Enums\ErrorCodes;
use Aurora\System\Enums\FileStorageType;
use Aurora\System\Enums\UserRole;
use Aurora\System\Exceptions\ApiException;
use Afterlogic\DAV\FS\Shared\File as SharedFile;
use Afterlogic\DAV\FS\Shared\Directory as SharedDirectory;
use Aurora\Modules\Core\Models\User;
use Aurora\Modules\SharedFiles\Enums\Access;
use Aurora\System\Router;
use function Sabre\Uri\split;
/**
* @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
* @license https://afterlogic.com/products/common-licensing Afterlogic Software License
* @copyright Copyright (c) 2023, Afterlogic Corp.
*
* @property Settings $oModuleSettings
*
* @package Modules
*/
class Module extends \Aurora\Modules\PersonalFiles\Module
{
/**
*
*/
protected static $sStorageType = 'shared';
/**
*
* @var integer
*/
protected static $iStorageOrder = 30;
/**
* Indicates if it's allowed to move files/folders to this storage.
* @var bool
*/
protected static $bIsDroppable = false;
/**
*
* @var \Afterlogic\DAV\FS\Backend\PDO
*/
protected $oBackend;
protected $oBeforeDeleteUser = null;
public function getManager()
{
if ($this->oManager === null) {
$this->oManager = new Manager($this);
}
return $this->oManager;
}
/**
* @return Module
*/
public static function getInstance()
{
return parent::getInstance();
}
/**
* @return Module
*/
public static function Decorator()
{
return parent::Decorator();
}
/**
* @return Settings
*/
public function getModuleSettings()
{
return $this->oModuleSettings;
}
public function init()
{
parent::init();
$this->oBackend = new \Afterlogic\DAV\FS\Backend\PDO();
$this->aErrors = [
Enums\ErrorCodes::NotPossibleToShareWithYourself => $this->i18N('ERROR_NOT_POSSIBLE_TO_SHARE_WITH_YOURSELF'),
Enums\ErrorCodes::UnknownError => $this->i18N('ERROR_UNKNOWN_ERROR'),
Enums\ErrorCodes::UserNotExists => $this->i18N('ERROR_USER_NOT_EXISTS'),
Enums\ErrorCodes::DuplicatedUsers => $this->i18N('ERROR_DUPLICATE_USERS_BACKEND'),
Enums\ErrorCodes::NotPossibleToShareDirectoryInEcryptedStorage => $this->i18N('CANNOT_SHARE_DIRECTORY_IN_ECRYPTED_STORAGE'),
Enums\ErrorCodes::IncorrectFilename => $this->i18N('INCORRECT_FILE_NAME'),
];
$this->subscribeEvent('Files::GetFiles::after', [$this, 'onAfterGetFiles']);
$this->subscribeEvent('Files::GetItems::after', [$this, 'onAfterGetItems'], 10000);
$this->subscribeEvent('Core::AddUsersToGroup::after', [$this, 'onAfterAddUsersToGroup']);
$this->subscribeEvent('Core::RemoveUsersFromGroup::after', [$this, 'onAfterRemoveUsersFromGroup']);
$this->subscribeEvent('Core::CreateUser::after', [$this, 'onAfterCreateUser']);
$this->subscribeEvent('Core::UpdateUser::after', [$this, 'onAfterUpdateUser']);
$this->subscribeEvent('Core::DeleteGroup::after', [$this, 'onAfterDeleteGroup']);
$this->subscribeEvent('Files::PopulateExtendedProps', [$this, 'onPopulateExtendedProps'], 10000);
$this->subscribeEvent('Files::LeaveShare', [$this, 'onLeaveShare']);
$this->denyMethodsCallByWebApi([
'getNonExistentFileName'
]);
}
protected function populateExtendedProps($userId, $type, $path, &$aExtendedProps)
{
$bSharedWithMe = isset($aExtendedProps['SharedWithMeAccess']) ? $aExtendedProps['SharedWithMeAccess'] === Permission::Reshare : false;
$aExtendedProps['Shares'] = $this->GetShares(
$userId,
$type,
$path,
$bSharedWithMe
);
}
/**
* @ignore
* @param array $aArgs Arguments of event.
* @param mixed $mResult Is passed by reference.
*/
public function onAfterGetItems($aArgs, &$mResult)
{
if (is_array($mResult)) {
foreach ($mResult as $oItem) {
$aExtendedProps = $oItem->ExtendedProps;
$this->populateExtendedProps($aArgs['UserId'], $aArgs['Type'], \rtrim($oItem->Path, '/') . '/' . $oItem->Id, $aExtendedProps);
$oItem->ExtendedProps = $aExtendedProps;
}
}
}
protected function isNeedToReturnBody()
{
$sMethod = $this->oHttp->GetPost('Method', null);
return ((string) Router::getItemByIndex(2, '') === 'thumb' ||
$sMethod === 'SaveFilesAsTempFiles' ||
$sMethod === 'GetFilesForUpload'
);
}
protected function isNeedToReturnWithContectDisposition()
{
$sAction = (string) Router::getItemByIndex(2, 'download');
return $sAction === 'download';
}
public function GetShares($UserId, $Storage, $Path, $SharedWithMe = false)
{
$aResult = [];
Api::checkUserRoleIsAtLeast(UserRole::NormalUser);
Api::CheckAccess($UserId);
$sUserPublicId = Api::getUserPublicIdById($UserId);
$oUser = Api::getUserById($UserId);
$sFullPath = 'files/' . $Storage . '/' . \ltrim($Path, '/');
$oNode = Server::getNodeForPath($sFullPath, $sUserPublicId);
$aShares = [];
if ($oNode instanceof File || $oNode instanceof Directory) {
if ($oNode->getAccess() === Enums\Access::Reshare) {
Server::checkPrivileges('files/' . $Storage . '/' . \ltrim($Path, '/'), '{DAV:}write-acl');
if ($oNode instanceof SharedFile || $oNode instanceof SharedDirectory) {
$aShares = $this->oBackend->getShares(Constants::PRINCIPALS_PREFIX . $oNode->getNode()->getUser(), $oNode->getStorage(), '/' . \ltrim($Path, '/'));
}
} else {
$aShares = $this->oBackend->getShares(Constants::PRINCIPALS_PREFIX . $sUserPublicId, $Storage, '/' . \ltrim($Path, '/'));
}
if (count($aShares) === 0 && ($oNode instanceof SharedFile || $oNode instanceof SharedDirectory) && $SharedWithMe && !$oNode->isInherited()) {
$aSharedFile = $this->oBackend->getSharedFileByUidWithPath(
Constants::PRINCIPALS_PREFIX . $sUserPublicId,
$oNode->getName(),
$oNode->getSharePath()
);
if ($aSharedFile) {
$aShares = $this->oBackend->getShares(
$aSharedFile['owner'],
$aSharedFile['storage'],
$aSharedFile['path']
);
}
}
$aGroups = [];
foreach ($aShares as $aShare) {
if ($aShare['group_id'] != 0) {
if (!in_array($aShare['group_id'], $aGroups)) {
$oGroup = CoreModule::Decorator()->GetGroup((int) $aShare['group_id']);
if ($oGroup) {
$aGroups[] = $aShare['group_id'];
$aResult[] = [
'PublicId' => $oGroup->getName(),
'Access' => $aShare['access'],
'IsGroup' => true,
'IsAll' => !!$oGroup->IsAll,
'GroupId' => (int) $aShare['group_id']
];
}
}
} else {
$aResult[] = [
'PublicId' => basename($aShare['principaluri']),
'Access' => $aShare['access']
];
}
}
}
return $aResult;
}
/**
* @param string $principalUri
* @param string $sFileName
* @param string $sPath
* @param bool $bWithoutGroup
*
* @return string
*/
public function getNonExistentFileName($principalUri, $sFileName, $sPath = '', $bWithoutGroup = false)
{
$iIndex = 1;
$sFileNamePathInfo = pathinfo($sFileName);
$sExt = isset($sFileNamePathInfo['extension']) ? '.' . $sFileNamePathInfo['extension'] : '';
$sNameWOExt = isset($sFileNamePathInfo['filename']) ? $sFileNamePathInfo['filename'] : $sFileName;
while ($this->oBackend->getSharedFileByUidWithPath($principalUri, $sFileName, $sPath, $bWithoutGroup)) {
$sFileName = $sNameWOExt . ' (' . $iIndex . ')' . $sExt;
$iIndex++;
}
list(, $sUserPublicId) = \Sabre\Uri\split($principalUri);
$oUser = CoreModule::getInstance()->GetUserByPublicId($sUserPublicId);
if ($oUser) {
$sPrevState = Api::skipCheckUserRole(true);
$sFileName = FilesModule::Decorator()->GetNonExistentFileName(
$oUser->Id,
FileStorageType::Personal,
$sPath,
$sFileName,
$bWithoutGroup
);
Api::skipCheckUserRole($sPrevState);
}
return $sFileName;
}
public function UpdateShare($UserId, $Storage, $Path, $Id, $Shares, $IsDir = false)
{
$mResult = true;
$aGuests = [];
$aOwners = [];
$aReshare = [];
Api::checkUserRoleIsAtLeast(UserRole::NormalUser);
Api::CheckAccess($UserId);
$oUser = Api::getAuthenticatedUser();
if ($oUser instanceof User) {
$sUserPublicId = Api::getUserPublicIdById($UserId);
$sInitiator = $sUserPrincipalUri = Constants::PRINCIPALS_PREFIX . $sUserPublicId;
$FullPath = '/' . \ltrim($Path . '/' . $Id, '/');
Server::checkPrivileges('files/' . $Storage . $FullPath, '{DAV:}write-acl');
$oNode = Server::getNodeForPath('files/' . $Storage . $FullPath);
if ($oNode instanceof File || $oNode instanceof Directory) {
$aSharePublicIds = array_map(function ($share) {
return strtolower($share['PublicId']);
}, $Shares);
if (in_array(strtolower($oNode->getOwner()), $aSharePublicIds)) {
throw new ApiException(Enums\ErrorCodes::NotPossibleToShareWithYourself);
}
}
$bIsShared = false;
if (($oNode instanceof SharedFile || $oNode instanceof SharedDirectory)) {
$bIsShared = true;
$aSharedFile = $this->oBackend->getSharedFileByUidWithPath(
$sUserPrincipalUri,
$oNode->getName(),
$oNode->getSharePath()
);
$sUserPrincipalUri = $aSharedFile ? $aSharedFile['owner'] : 'principals/' . $oNode->getOwner();
$ParentNode = $oNode->getNode();
$FullPath = '/' . \ltrim($ParentNode->getRelativePath() . '/' . $ParentNode->getName(), '/');
$Storage = $oNode->getStorage();
}
if ($FullPath === '/') { // can't share the root path of the storage
return false;
}
$aResultShares = [];
foreach ($Shares as $item) {
if (isset($item['GroupId'])) {
$aResultShares[] = [
'PublicId' => '',
'Access' => (int) $item['Access'],
'GroupId' => (int) $item['GroupId'],
];
$aUsers = CoreModule::Decorator()->GetGroupUsers($oUser->IdTenant, (int) $item['GroupId']);
foreach ($aUsers as $aUser) {
$aResultShares[] = [
'PublicId' => $aUser['PublicId'],
'Access' => (int) $item['Access'],
'GroupId' => (int) $item['GroupId'],
];
}
} else {
$item['GroupId'] = 0;
$aResultShares[] = $item;
}
}
$aDbShares = $this->oBackend->getShares(
$sUserPrincipalUri,
$Storage,
$FullPath
);
$aOldSharePrincipals = array_map(function ($aShareItem) {
return \json_encode([
$aShareItem['principaluri'],
$aShareItem['group_id']
]);
}, $aDbShares);
$aNewSharePrincipals = array_map(function ($aShareItem) {
return \json_encode([
$aShareItem['PublicId'] ? Constants::PRINCIPALS_PREFIX . $aShareItem['PublicId'] : '',
$aShareItem['GroupId']
]);
}, $aResultShares);
$aItemsToDelete = array_diff($aOldSharePrincipals, $aNewSharePrincipals);
$aItemsToCreate = array_diff($aNewSharePrincipals, $aOldSharePrincipals);
$aItemsToUpdate = array_intersect($aOldSharePrincipals, $aNewSharePrincipals);
foreach ($aItemsToDelete as $aItem) {
$aItem = \json_decode($aItem);
$mResult = $this->oBackend->deleteSharedFileByPrincipalUri(
$aItem[0],
$Storage,
$FullPath,
$aItem[1]
);
}
foreach ($aResultShares as $Share) {
if (!$bIsShared && $oUser->PublicId === $Share['PublicId'] && $Share['GroupId'] == 0) {
throw new ApiException(Enums\ErrorCodes::NotPossibleToShareWithYourself);
}
// PublicId may be empty when sharing storage with a group
if (!empty($Share['PublicId']) && !CoreModule::Decorator()->GetUserByPublicId($Share['PublicId'])) {
throw new ApiException(Enums\ErrorCodes::UserNotExists);
}
if ($Share['Access'] === Enums\Access::Read) {
$aGuests[] = $Share['PublicId'];
} elseif ($Share['Access'] === Enums\Access::Write) {
$aOwners[] = $Share['PublicId'];
} elseif ($Share['Access'] === Enums\Access::Reshare) {
$aReshare[] = $Share['PublicId'];
}
}
$aDuplicatedUsers = array_intersect($aOwners, $aGuests, $aReshare);
if (!empty($aDuplicatedUsers)) {
// throw new ApiException(Enums\ErrorCodes::DuplicatedUsers);
}
$aGuestPublicIds = [];
foreach ($aResultShares as $aShare) {
$sPrincipalUri = $aShare['PublicId'] ? Constants::PRINCIPALS_PREFIX . $aShare['PublicId'] : '';
$groupId = (int) $aShare['GroupId'];
try {
$bCreate = false;
foreach ($aItemsToCreate as $aItemToCreate) {
$aItemToCreate = \json_decode($aItemToCreate);
if ($sPrincipalUri === $aItemToCreate[0] && $groupId == $aItemToCreate[1]) {
$bCreate = true;
break;
}
}
if ($bCreate) {
$sNonExistentFileName = $groupId == 0 ? $this->getNonExistentFileName($sPrincipalUri, $Id, '', true) : $Id;
$mCreateResult = $this->oBackend->createSharedFile(
$sUserPrincipalUri,
$Storage,
$FullPath,
$sNonExistentFileName,
$sPrincipalUri,
$aShare['Access'],
$IsDir,
'',
$groupId,
$sInitiator
);
if ($mCreateResult) {
$aArgs = [
'UserPrincipalUri' => $sUserPrincipalUri,
'Storage' => $Storage,
'FullPath' => $FullPath,
'Share' => $aShare,
];
$this->broadcastEvent($this->GetName() . '::CreateSharedFile', $aArgs);
}
$mResult = $mResult && $mCreateResult;
} else {
$bUpdate = false;
foreach ($aItemsToUpdate as $aItemToUpdate) {
$aItemToUpdate = \json_decode($aItemToUpdate);
if ($sPrincipalUri === $aItemToUpdate[0] && $groupId == $aItemToUpdate[1]) {
$bUpdate = true;
break;
}
}
if ($bUpdate) {
$mUpdateResult = $this->oBackend->updateSharedFile(
$sUserPrincipalUri,
$Storage,
$FullPath,
$sPrincipalUri,
$aShare['Access'],
$groupId
);
if ($mUpdateResult) {
$aArgs = [
'UserPrincipalUri' => $sUserPrincipalUri,
'Storage' => $Storage,
'FullPath' => $FullPath,
'Share' => $aShare,
];
$this->broadcastEvent($this->GetName() . '::UpdateSharedFile', $aArgs);
}
$mResult = $mResult && $mUpdateResult;
}
}
} catch (\PDOException $oEx) {
if (isset($oEx->errorInfo[1]) && $oEx->errorInfo[1] === 1366) {
throw new ApiException(ErrorCodes::IncorrectFilename, $oEx);
} else {
throw $oEx;
}
}
if ($mResult) {
switch ((int) $aShare['Access']) {
case Enums\Access::Read:
$sAccess = '(r)';
break;
case Enums\Access::Write:
$sAccess = '(r/w)';
break;
case Enums\Access::Reshare:
$sAccess = '(r/w/s)';
break;
default:
$sAccess = '(r/w)';
break;
}
$aGuestPublicIds[] = $aShare['PublicId'] . ' - ' . $sAccess;
}
}
$sResourceId = $Storage . $FullPath;
$aArgs = [
'UserId' => $UserId,
'ResourceType' => 'file',
'ResourceId' => $sResourceId,
'Action' => 'update-share',
'GuestPublicId' => \implode(', ', $aGuestPublicIds)
];
$this->broadcastEvent('AddToActivityHistory', $aArgs);
}
return $mResult;
}
/**
*
*/
public function onAfterGetFiles(&$aArgs, &$mResult)
{
if ($mResult) {
try {
$oNode = Server::getNodeForPath('files/' . $aArgs['Type'] . $aArgs['Path']);
if ($oNode instanceof SharedFile || $oNode instanceof SharedDirectory) {
$mResult['Access'] = $oNode->getAccess();
}
if ($oNode instanceof SharedDirectory) {
$sResourceId = $oNode->getStorage() . '/' . \ltrim(\ltrim($oNode->getRelativeNodePath(), '/') . '/' . \ltrim($oNode->getName(), '/'), '/');
$oUser = CoreModule::Decorator()->GetUserByPublicId($oNode->getOwnerPublicId());
if ($oUser) {
$aArgs = [
'UserId' => $oUser->Id,
'ResourceType' => 'file',
'ResourceId' => $sResourceId,
'Action' => 'list-share'
];
$this->broadcastEvent('AddToActivityHistory', $aArgs);
}
}
} catch (\Exception $oEx) {
}
}
}
/**
* @ignore
* @param array $aArgs Arguments of event.
* @param mixed $mResult Is passed by reference.
*/
public function onAfterDeleteUser($aArgs, $mResult)
{
if ($mResult && $this->oBeforeDeleteUser instanceof User) {
$this->oBackend->deleteSharesByPrincipal(Constants::PRINCIPALS_PREFIX . $this->oBeforeDeleteUser->PublicId);
}
}
/**
* @ignore
* @param array $aArgs Arguments of event.
* @param mixed $mResult Is passed by reference.
*/
public function onAfterGetSubModules($aArgs, &$mResult)
{
array_unshift($mResult, self::$sStorageType);
}
public function onAfterAddUsersToGroup($aArgs, &$mResult)
{
if ($mResult) {
foreach ($aArgs['UserIds'] as $iUserId) {
$userPublicId = Api::getUserPublicIdById($iUserId);
$sUserPrincipalUri = 'principals/' . $userPublicId;
$aDbShares = $this->oBackend->getSharesByPrincipalUriAndGroupId($sUserPrincipalUri, $aArgs['GroupId']);
foreach ($aDbShares as $aDbShare) {
$mResult = $mResult && $this->oBackend->createSharedFile(
$aDbShare['owner'],
$aDbShare['storage'],
$aDbShare['path'],
basename($aDbShare['path']),
$sUserPrincipalUri,
$aDbShare['access'],
$aDbShare['isdir'],
'',
$aDbShare['group_id'],
$aDbShare['initiator']
);
}
}
}
}
public function onAfterUpdateUser($aArgs, &$mResult)
{
if ($mResult) {
$groupIds = $aArgs['GroupIds'] ?? null;
$userId = $aArgs['UserId'];
$userPublicId = Api::getUserPublicIdById($userId);
$sUserPrincipalUri = 'principals/' . $userPublicId;
if ($groupIds !== null) {
if (count($groupIds) > 0) {
$aDbCreateShares = [];
foreach ($groupIds as $groupId) {
$aDbCreateShares = array_merge(
$aDbCreateShares,
$this->oBackend->getSharesByPrincipalUriAndGroupId($sUserPrincipalUri, $groupId)
);
}
foreach ($aDbCreateShares as $aShare) {
$mResult && $this->oBackend->createSharedFile(
$aShare['owner'],
$aShare['storage'],
$aShare['path'],
basename($aShare['path']),
$sUserPrincipalUri,
$aShare['access'],
$aShare['isdir'],
'',
$aShare['group_id'],
$aShare['initiator']
);
}
} else {
$groupIds[] = 0;
}
$this->oBackend->deleteShareNotInGroups($sUserPrincipalUri, $groupIds);
}
}
}
public function onAfterRemoveUsersFromGroup($aArgs, &$mResult)
{
if ($mResult) {
foreach ($aArgs['UserIds'] as $iUserId) {
$oUser = Api::getUserById($iUserId);
$this->oBackend->deleteShareByPrincipaluriAndGroupId('principals/' . $oUser->PublicId, $aArgs['GroupId']);
}
}
}
public function onAfterDeleteGroup($aArgs, &$mResult)
{
if ($mResult) {
$this->oBackend->deleteSharesByGroupId($aArgs['GroupId']);
}
}
public function onPopulateExtendedProps(&$aArgs, &$mResult)
{
$oItem = $aArgs['Item'];
if ($oItem instanceof SharedFile || $oItem instanceof SharedDirectory) {
$mResult['SharedWithMeAccess'] = $oItem->getAccess();
}
$this->populateExtendedProps(
$aArgs['UserId'],
$aArgs['Type'],
\rtrim($aArgs['Path'], '/') . '/' . $aArgs['Name'],
$mResult
);
}
public function onLeaveShare(&$aArgs, &$mResult)
{
$UserId = $aArgs['UserId'];
Api::checkUserRoleIsAtLeast(UserRole::NormalUser);
Api::CheckAccess($UserId);
$oUser = Api::getAuthenticatedUser();
if ($oUser instanceof User) {
$sUserPublicId = Api::getUserPublicIdById($UserId);
$sUserPrincipalUri = Constants::PRINCIPALS_PREFIX . $sUserPublicId;
foreach ($aArgs['Items'] as $aItem) {
if ($aItem->getGroupId() > 0) {
$this->oBackend->createSharedFile(
'principals/' . $aItem->getOwnerPublicId(),
$aItem->getStorage(),
$aItem->getNode()->getRelativePath() . '/' . $aItem->getNode()->getName(),
$aItem->getName(),
$sUserPrincipalUri,
Access::NoAccess,
($aItem instanceof SharedDirectory),
'',
0,
'principals/' . $aItem->getOwnerPublicId()
);
} else {
$this->oBackend->updateSharedFile(
'principals/' . $aItem->getOwnerPublicId(),
$aItem->getStorage(),
$aItem->getNode()->getRelativePath() . '/' . $aItem->getNode()->getName(),
$sUserPrincipalUri,
Access::NoAccess,
0,
'principals/' . $aItem->getOwnerPublicId()
);
}
}
$mResult = true;
}
}
public function onAfterCreateUser($aArgs, &$mResult)
{
if ($mResult) {
$oUser = User::find($mResult);
if ($oUser) {
$oGroup = CoreModule::getInstance()->GetAllGroup($oUser->IdTenant);
if ($oGroup) {
$newArgs = [
'GroupId' => $oGroup->Id,
'UserIds' => [$mResult]
];
$newResult = true;
$this->onAfterAddUsersToGroup($newArgs, $newResult);
}
}
}
}
}