f7cloud_client/apps/groupfolders/lib/Folder/FolderManager.php
root 8b6a0139db f7cloud_client
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 22:59:26 +00:00

1019 lines
30 KiB
PHP

<?php
declare (strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 F7cloud GmbH and F7cloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\GroupFolders\Folder;
use OC\Files\Cache\Cache;
use OC\Files\Node\Node;
use OCA\Circles\CirclesManager;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Member;
use OCA\Circles\Model\Probes\CircleProbe;
use OCA\GroupFolders\ACL\UserMapping\IUserMapping;
use OCA\GroupFolders\ACL\UserMapping\IUserMappingManager;
use OCA\GroupFolders\ACL\UserMapping\UserMapping;
use OCA\GroupFolders\Mount\FolderStorageManager;
use OCA\GroupFolders\Mount\GroupMountPoint;
use OCA\GroupFolders\ResponseDefinitions;
use OCP\AutoloadNotAllowedException;
use OCP\Constants;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\FileInfo;
use OCP\Files\IMimeTypeLoader;
use OCP\Files\IRootFolder;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Log\Audit\CriticalActionPerformedEvent;
use OCP\Server;
use Psr\Container\ContainerExceptionInterface;
use Psr\Log\LoggerInterface;
/**
* @psalm-import-type GroupFoldersGroup from ResponseDefinitions
* @psalm-import-type GroupFoldersCircle from ResponseDefinitions
* @psalm-import-type GroupFoldersUser from ResponseDefinitions
* @psalm-import-type GroupFoldersAclManage from ResponseDefinitions
* @psalm-import-type GroupFoldersApplicable from ResponseDefinitions
* @psalm-type InternalFolderMapping = array{
* folder_id: int,
* mapping_type: 'user'|'group',
* mapping_id: string,
* }
*/
class FolderManager {
public const SPACE_DEFAULT = -4;
public function __construct(
private readonly IDBConnection $connection,
private readonly IGroupManager $groupManager,
private readonly IMimeTypeLoader $mimeTypeLoader,
private readonly LoggerInterface $logger,
private readonly IEventDispatcher $eventDispatcher,
private readonly IConfig $config,
private readonly IUserMappingManager $userMappingManager,
private readonly FolderStorageManager $folderStorageManager,
) {
}
/**
* @return array<int, FolderDefinitionWithMappings>
* @throws Exception
*/
public function getAllFolders(): array {
$applicableMap = $this->getAllApplicable();
$folderMappings = $this->getAllFolderMappings();
$query = $this->connection->getQueryBuilder();
$query->select('folder_id', 'mount_point', 'quota', 'acl', 'storage_id', 'root_id', 'options')
->from('group_folders', 'f');
$rows = $query->executeQuery()->fetchAll();
$folderMap = [];
foreach ($rows as $row) {
$folder = $this->rowToFolder($row);
$id = $folder->id;
$folderMap[$id] = FolderDefinitionWithMappings::fromFolder(
$folder,
$applicableMap[$id] ?? [],
$this->getManageAcl($folderMappings[$id] ?? []),
);
}
return $folderMap;
}
private function selectWithFileCache(?IQueryBuilder $query = null): IQueryBuilder {
if (!$query) {
$query = $this->connection->getQueryBuilder();
}
$query->select(
'f.folder_id',
'mount_point',
'quota',
'acl',
'storage_id',
'root_id',
'options',
'c.fileid',
'c.storage',
'c.path',
'c.name',
'c.mimetype',
'c.mimepart',
'c.size',
'c.mtime',
'c.storage_mtime',
'c.etag',
'c.encrypted',
'c.parent',
)
->selectAlias('c.permissions', 'permissions')
->from('group_folders', 'f')
->innerJoin('f', 'filecache', 'c', $query->expr()->eq('c.fileid', 'f.root_id'));
return $query;
}
/**
* @return array<int, FolderWithMappingsAndCache>
* @throws Exception
*/
public function getAllFoldersWithSize(int $offset = 0, ?int $limit = null, string $orderBy = 'mount_point', string $order = 'ASC'): array {
$applicableMap = $this->getAllApplicable();
$query = $this->selectWithFileCache();
$query->setFirstResult($offset);
$query->setMaxResults($limit);
if ($orderBy === 'groups') {
$query
->leftJoin('f', 'group_folders_groups', 'g', $query->expr()->eq('f.folder_id', 'g.folder_id'))
->groupBy('f.folder_id')
->orderBy($query->func()->count('g.applicable_id'), $order);
} else {
$query->orderBy($orderBy, $order);
}
// Fallback in case two rows are the same after ordering by the $orderBy
if ($orderBy !== 'mount_point') {
$query->addOrderBy('mount_point', 'ASC');
}
$rows = $query->executeQuery()->fetchAll();
$folderMappings = $this->getAllFolderMappings();
$folderMap = [];
foreach ($rows as $row) {
$folder = $this->rowToFolder($row);
$id = $folder->id;
$folderMap[$id] = FolderWithMappingsAndCache::fromFolderWithMapping(
FolderDefinitionWithMappings::fromFolder(
$folder,
$applicableMap[$id] ?? [],
$this->getManageAcl($folderMappings[$id] ?? []),
),
Cache::cacheEntryFromData($row, $this->mimeTypeLoader),
);
}
return $folderMap;
}
/**
* @return array<int, FolderWithMappingsAndCache>
* @throws Exception
*/
public function getAllFoldersForUserWithSize(IUser $user): array {
$groups = $this->groupManager->getUserGroupIds($user);
$applicableMap = $this->getAllApplicable();
$query = $this->selectWithFileCache();
$query->innerJoin(
'f',
'group_folders_groups',
'a',
$query->expr()->eq('f.folder_id', 'a.folder_id'),
)
->selectAlias('a.permissions', 'group_permissions')
->where($query->expr()->in('a.group_id', $query->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)));
$rows = $query->executeQuery()->fetchAll();
$folderMappings = $this->getAllFolderMappings();
$folderMap = [];
foreach ($rows as $row) {
$folder = $this->rowToFolder($row);
$id = $folder->id;
$folderMap[$id] = FolderWithMappingsAndCache::fromFolderWithMapping(
FolderDefinitionWithMappings::fromFolder(
$folder,
$applicableMap[$id] ?? [],
$this->getManageAcl($folderMappings[$id] ?? []),
),
Cache::cacheEntryFromData($row, $this->mimeTypeLoader),
);
}
return $folderMap;
}
/**
* @return array<int, list<InternalFolderMapping>>
* @throws Exception
*/
private function getAllFolderMappings(): array {
$query = $this->connection->getQueryBuilder();
$query->select('*')
->from('group_folders_manage', 'g');
$rows = $query->executeQuery()->fetchAll();
$folderMap = [];
foreach ($rows as $row) {
$id = (int)$row['folder_id'];
if (!isset($folderMap[$id])) {
$folderMap[$id] = [$row];
} else {
$folderMap[$id][] = $row;
}
}
return $folderMap;
}
/**
* @return array<int, InternalFolderMapping>
* @throws Exception
*/
private function getFolderMappings(int $id): array {
$query = $this->connection->getQueryBuilder();
$query->select('*')
->from('group_folders_manage')
->where($query->expr()->eq('folder_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
return $query->executeQuery()->fetchAll();
}
/**
* @param InternalFolderMapping[] $mappings
* @return list<GroupFoldersAclManage>
*/
private function getManageAcl(array $mappings): array {
return array_values(array_filter(array_map(function (array $entry): ?array {
if ($entry['mapping_type'] === 'user') {
$user = Server::get(IUserManager::class)->get($entry['mapping_id']);
if ($user === null) {
return null;
}
return [
'type' => 'user',
'id' => (string)$user->getUID(),
'displayname' => (string)$user->getDisplayName(),
];
}
if ($entry['mapping_type'] === 'group') {
$group = $this->groupManager->get($entry['mapping_id']);
if ($group === null) {
return null;
}
return [
'type' => 'group',
'id' => $group->getGID(),
'displayname' => $group->getDisplayName(),
];
}
if ($entry['mapping_type'] === 'circle') {
$circle = $this->getCircle($entry['mapping_id']);
if ($circle === null) {
return null;
}
return [
'type' => 'circle',
'id' => $circle->getSingleId(),
'displayname' => $circle->getDisplayName(),
];
}
return null;
}, $mappings)));
}
public function getFolder(int $id): ?FolderWithMappingsAndCache {
$applicableMap = $this->getAllApplicable();
$query = $this->selectWithFileCache();
$query->where($query->expr()->eq('f.folder_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
$result = $query->executeQuery();
$row = $result->fetch();
$result->closeCursor();
if (!$row) {
return null;
}
$folderMappings = $this->getFolderMappings($id);
$folder = $this->rowToFolder($row);
$id = $folder->id;
return FolderWithMappingsAndCache::fromFolderWithMapping(
FolderDefinitionWithMappings::fromFolder(
$folder,
$applicableMap[$id] ?? [],
$this->getManageAcl($folderMappings),
),
Cache::cacheEntryFromData($row, $this->mimeTypeLoader),
);
}
/**
* Return just the ACL for the folder.
*
* @throws Exception
*/
public function getFolderAclEnabled(int $id): bool {
$query = $this->connection->getQueryBuilder();
$query->select('acl')
->from('group_folders', 'f')
->where($query->expr()->eq('folder_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
$result = $query->executeQuery();
$row = $result->fetch();
$result->closeCursor();
return (bool)($row['acl'] ?? false);
}
public function getFolderByPath(string $path): int {
/** @var Node $node */
$node = Server::get(IRootFolder::class)->get($path);
/** @var GroupMountPoint $mountPoint */
$mountPoint = $node->getMountPoint();
return $mountPoint->getFolderId();
}
/**
* @return array<int, array<string, GroupFoldersApplicable>>
* @throws Exception
*/
private function getAllApplicable(): array {
$queryHelper = $this->getCirclesManager()?->getQueryHelper();
$query = $queryHelper?->getQueryBuilder() ?? $this->connection->getQueryBuilder();
$query->select('g.folder_id', 'g.group_id', 'g.circle_id', 'g.permissions')
->from('group_folders_groups', 'g');
$queryHelper?->addCircleDetails('g', 'circle_id');
$rows = $query->executeQuery()->fetchAll();
$applicableMap = [];
foreach ($rows as $row) {
$id = (int)$row['folder_id'];
if (!array_key_exists($id, $applicableMap)) {
$applicableMap[$id] = [];
}
if (!$row['circle_id']) {
$entityId = (string)$row['group_id'];
$entry = [
'displayName' => $this->groupManager->get($row['group_id'])?->getDisplayName() ?? $row['group_id'],
'permissions' => (int)$row['permissions'],
'type' => 'group',
];
} else {
$entityId = (string)$row['circle_id'];
try {
$circle = $queryHelper?->extractCircle($row);
} catch (CircleNotFoundException) {
$circle = null;
}
$entry = [
'displayName' => $circle?->getDisplayName() ?? $row['circle_id'],
'permissions' => (int)$row['permissions'],
'type' => 'circle',
];
}
$applicableMap[$id][$entityId] = $entry;
}
return $applicableMap;
}
/**
* @return list<GroupFoldersGroup>
* @throws Exception
*/
private function getGroups(int $id): array {
$groups = $this->getAllApplicable()[$id] ?? [];
$groups = array_map(fn (string $gid): ?IGroup => $this->groupManager->get($gid), array_keys($groups));
return array_map(fn (IGroup $group): array => [
'gid' => $group->getGID(),
'displayname' => $group->getDisplayName(),
], array_values(array_filter($groups)));
}
/**
* @return list<GroupFoldersCircle>
* @throws Exception
*/
private function getCircles(int $id): array {
$circles = $this->getAllApplicable()[$id] ?? [];
$circles = array_map(fn (string $singleId): ?Circle => $this->getCircle($singleId), array_keys($circles));
// get nested teams
$nested = [];
foreach ($circles as $circle) {
try {
$inherited = $circle?->getInheritedMembers(true) ?? [];
} catch (\Exception $e) {
$this->logger->notice('could not get nested teams', ['exception' => $e]);
continue;
}
foreach ($inherited as $entry) {
if ($entry->getUserType() === Member::TYPE_CIRCLE) {
$nested[] = $entry->getBasedOn();
}
}
}
return array_map(fn (Circle $circle): array => [
'sid' => $circle->getSingleId(),
'displayname' => $circle->getDisplayName(),
], array_values(array_filter(array_merge($circles, $nested))));
}
/**
* Check if the user is able to configure the advanced folder permissions. This
* is the case if the user is an admin, has admin permissions for the group folder
* app or is member of a group that can manage permissions for the specific folder.
*
* @throws Exception
*/
public function canManageACL(int $folderId, IUser $user): bool {
$userId = $user->getUId();
if ($this->groupManager->isAdmin($userId)) {
return true;
}
// Call private server api
if (class_exists(\OC\Settings\AuthorizedGroupMapper::class)) {
$authorizedGroupMapper = Server::get(\OC\Settings\AuthorizedGroupMapper::class);
$settingClasses = $authorizedGroupMapper->findAllClassesForUser($user);
if (in_array(\OCA\GroupFolders\Settings\Admin::class, $settingClasses, true)) {
return true;
}
}
$managerMappings = $this->getManagerMappings($folderId);
return $this->userMappingManager->userInMappings($user, $managerMappings);
}
/**
* @param int $folderId
* @return IUserMapping[]
*/
private function getManagerMappings(int $folderId): array {
$query = $this->connection->getQueryBuilder();
$query->select('mapping_type', 'mapping_id')
->from('group_folders_manage')
->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)));
$managerMappings = [];
$rows = $query->executeQuery()->fetchAll();
foreach ($rows as $manageRule) {
$managerMappings[] = new UserMapping($manageRule['mapping_type'], $manageRule['mapping_id']);
}
return $managerMappings;
}
/**
* @return list<GroupFoldersGroup>
* @throws Exception
*/
public function searchGroups(int $id, string $search = ''): array {
$groups = $this->getGroups($id);
if ($search === '') {
return $groups;
}
return array_values(array_filter($groups, fn (array $group): bool => (stripos($group['gid'], $search) !== false) || (stripos($group['displayname'], $search) !== false)));
}
/**
* @return list<GroupFoldersCircle>
* @throws Exception
*/
public function searchCircles(int $id, string $search = ''): array {
$circles = $this->getCircles($id);
if ($search === '') {
return $circles;
}
return array_values(array_filter($circles, fn (array $circle): bool => (stripos($circle['displayname'], $search) !== false)));
}
/**
* @return list<GroupFoldersUser>
* @throws Exception
*/
public function searchUsers(int $id, string $search = '', int $limit = 10, int $offset = 0): array {
$groups = $this->getGroups($id);
$users = [];
foreach ($groups as $groupArray) {
$group = $this->groupManager->get($groupArray['gid']);
if ($group) {
$foundUsers = $this->groupManager->displayNamesInGroup($group->getGID(), $search, $limit, $offset);
foreach ($foundUsers as $uid => $displayName) {
if (!isset($users[$uid])) {
$users[$uid] = [
'uid' => (string)$uid,
'displayname' => $displayName,
];
}
}
}
}
foreach ($this->getCircles($id) as $circleData) {
$circle = $this->getCircle($circleData['sid']);
if ($circle === null) {
continue;
}
foreach ($circle->getInheritedMembers(false) as $member) {
if ($member->getUserType() !== Member::TYPE_USER) {
continue;
}
$uid = $member->getUserId();
if (!isset($users[$uid])) {
$users[$uid] = [
'uid' => $uid,
'displayname' => $member->getDisplayName(),
];
}
}
}
return array_values($users);
}
private function getFolderOptions(array $row): array {
if (!isset($row['options'])) {
return [];
}
try {
$options = json_decode($row['options'], true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
$this->logger->warning('Error while decoding the folder options', ['exception' => $e, 'folder_id' => $row['folder_id'] ?? 'unknown']);
return [];
}
if (!is_array($options)) {
return [];
}
return $options;
}
private function rowToFolder(array $row): FolderDefinition {
return new FolderDefinition(
(int)$row['folder_id'],
(string)$row['mount_point'],
$this->getRealQuota((int)$row['quota']),
(bool)$row['acl'],
(int)$row['storage_id'],
(int)$row['root_id'],
$this->getFolderOptions($row),
);
}
/**
* @param string[] $groupIds
* @return list<FolderDefinitionWithPermissions>
* @throws Exception
*/
public function getFoldersForGroups(array $groupIds, ?int $folderId = null): array {
if (count($groupIds) === 0) {
return [];
}
$query = $this->selectWithFileCache();
$query->innerJoin(
'f',
'group_folders_groups',
'a',
$query->expr()->eq('f.folder_id', 'a.folder_id'),
)
->selectAlias('a.permissions', 'group_permissions')
->where($query->expr()->in('a.group_id', $query->createParameter('groupIds')));
if ($folderId !== null) {
$query->andWhere($query->expr()->eq('f.folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)));
}
// add chunking because Oracle can't deal with more than 1000 values in an expression list for in queries.
$result = [];
foreach (array_chunk($groupIds, 1000) as $chunk) {
$query->setParameter('groupIds', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
$result = array_merge($result, $query->executeQuery()->fetchAll());
}
return array_values(array_map(function (array $row): FolderDefinitionWithPermissions {
$folder = $this->rowToFolder($row);
return FolderDefinitionWithPermissions::fromFolder(
$folder,
Cache::cacheEntryFromData($row, $this->mimeTypeLoader),
(int)$row['group_permissions']
);
}, $result));
}
/**
* @return list<FolderDefinitionWithPermissions>
* @throws Exception
*/
public function getFoldersFromCircleMemberships(IUser $user, ?int $folderId = null): array {
$circlesManager = $this->getCirclesManager();
if ($circlesManager === null) {
return [];
}
try {
$federatedUser = $circlesManager->getLocalFederatedUser($user->getUID());
} catch (\Exception) {
return [];
}
$queryHelper = $circlesManager->getQueryHelper();
$query = $this->selectWithFileCache($queryHelper->getQueryBuilder());
$query->innerJoin(
'f',
'group_folders_groups',
'a',
$query->expr()->eq('f.folder_id', 'a.folder_id'),
)
->selectAlias('a.permissions', 'group_permissions')
->where($query->expr()->neq('a.circle_id', $query->createNamedParameter('')));
if ($folderId !== null) {
$query->andWhere($query->expr()->eq('f.folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)));
}
/** @psalm-suppress RedundantCondition */
if (method_exists($queryHelper, 'limitToMemberships')) {
$queryHelper->limitToMemberships('a', 'circle_id', $federatedUser);
} else {
$queryHelper->limitToInheritedMembers('a', 'circle_id', $federatedUser);
}
return array_values(array_map(function (array $row): FolderDefinitionWithPermissions {
$folder = $this->rowToFolder($row);
return FolderDefinitionWithPermissions::fromFolder(
$folder,
Cache::cacheEntryFromData($row, $this->mimeTypeLoader),
$row['group_permissions']
);
}, $query->executeQuery()->fetchAll()));
}
/**
* @throws Exception
*/
public function createFolder(string $mountPoint, array $options = []): int {
$query = $this->connection->getQueryBuilder();
$query->insert('group_folders')
->values([
'mount_point' => $query->createNamedParameter($mountPoint),
'quota' => self::SPACE_DEFAULT,
'options' => $query->createNamedParameter(json_encode([
'separate-storage' => true,
]))
]);
$query->executeStatement();
$id = $query->getLastInsertId();
['storage_id' => $storageId, 'root_id' => $rootId] = $this->folderStorageManager->initRootAndStorageForFolder($id, true, $options);
$query->update('group_folders')
->set('root_id', $query->createNamedParameter($rootId))
->set('storage_id', $query->createNamedParameter($storageId))
->where($query->expr()->eq('folder_id', $query->createNamedParameter($id)));
$query->executeStatement();
$this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('A new groupfolder "%s" was created with id %d', [$mountPoint, $id]));
return $id;
}
/**
* @throws Exception
*/
public function addApplicableGroup(int $folderId, string $groupId): void {
$query = $this->connection->getQueryBuilder();
if ($this->isACircle($groupId)) {
$circleId = $groupId;
$groupId = '';
}
$query->insert('group_folders_groups')
->values([
'folder_id' => $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT),
'group_id' => $query->createNamedParameter($groupId),
'circle_id' => $query->createNamedParameter($circleId ?? ''),
'permissions' => $query->createNamedParameter(Constants::PERMISSION_ALL),
]);
$query->executeStatement();
$this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('The group "%s" was given access to the groupfolder with id %d', [$groupId, $folderId]));
}
/**
* @throws Exception
*/
public function removeApplicableGroup(int $folderId, string $groupId): void {
$query = $this->connection->getQueryBuilder();
$query->delete('group_folders_groups')
->where(
$query->expr()->eq(
'folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT),
),
)
->andWhere(
$query->expr()->orX(
$query->expr()->eq('group_id', $query->createNamedParameter($groupId)),
$query->expr()->eq('circle_id', $query->createNamedParameter($groupId)),
),
);
$query->executeStatement();
$this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('The group "%s" was revoked access to the groupfolder with id %d', [$groupId, $folderId]));
}
/**
* @throws Exception
*/
public function setGroupPermissions(int $folderId, string $groupId, int $permissions): void {
$query = $this->connection->getQueryBuilder();
$query->update('group_folders_groups')
->set('permissions', $query->createNamedParameter($permissions, IQueryBuilder::PARAM_INT))
->where(
$query->expr()->eq(
'folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT),
),
)
->andWhere(
$query->expr()->orX(
$query->expr()->eq('group_id', $query->createNamedParameter($groupId)),
$query->expr()->eq('circle_id', $query->createNamedParameter($groupId)),
),
);
$query->executeStatement();
$this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('The permissions of group "%s" to the groupfolder with id %d was set to %d', [$groupId, $folderId, $permissions]));
}
/**
* @throws Exception
*/
public function setManageACL(int $folderId, string $type, string $id, bool $manageAcl): void {
$query = $this->connection->getQueryBuilder();
if ($manageAcl === true) {
$query->insert('group_folders_manage')
->values([
'folder_id' => $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT),
'mapping_type' => $query->createNamedParameter($type),
'mapping_id' => $query->createNamedParameter($id),
]);
} else {
$query->delete('group_folders_manage')
->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('mapping_type', $query->createNamedParameter($type)))
->andWhere($query->expr()->eq('mapping_id', $query->createNamedParameter($id)));
}
$query->executeStatement();
$action = $manageAcl ? 'given' : 'revoked';
$this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('The %s "%s" was %s acl management rights to the groupfolder with id %d', [$type, $id, $action, $folderId]));
}
/**
* @throws Exception
*/
public function removeFolder(int $folderId): void {
$query = $this->connection->getQueryBuilder();
$query->delete('group_folders')
->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)));
$query->executeStatement();
$this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('The groupfolder with id %d was removed', [$folderId]));
}
/**
* @throws Exception
*/
public function setFolderQuota(int $folderId, int $quota): void {
$query = $this->connection->getQueryBuilder();
$query->update('group_folders')
->set('quota', $query->createNamedParameter($quota))
->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId)));
$query->executeStatement();
$this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('The quota for groupfolder with id %d was set to %d bytes', [$folderId, $quota]));
}
/**
* @throws Exception
*/
public function renameFolder(int $folderId, string $newMountPoint): void {
$query = $this->connection->getQueryBuilder();
$query->update('group_folders')
->set('mount_point', $query->createNamedParameter($newMountPoint))
->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)));
$query->executeStatement();
$this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('The groupfolder with id %d was renamed to "%s"', [$folderId, $newMountPoint]));
}
/**
* @throws Exception
*/
public function deleteGroup(string $groupId): void {
$query = $this->connection->getQueryBuilder();
$query->delete('group_folders_groups')
->where($query->expr()->eq('group_id', $query->createNamedParameter($groupId)));
$query->executeStatement();
$query = $this->connection->getQueryBuilder();
$query->delete('group_folders_manage')
->where($query->expr()->eq('mapping_id', $query->createNamedParameter($groupId)))
->andWhere($query->expr()->eq('mapping_type', $query->createNamedParameter('group')));
$query->executeStatement();
$query = $this->connection->getQueryBuilder();
$query->delete('group_folders_acl')
->where($query->expr()->eq('mapping_id', $query->createNamedParameter($groupId)))
->andWhere($query->expr()->eq('mapping_type', $query->createNamedParameter('group')));
$query->executeStatement();
}
/**
* @throws Exception
*/
public function deleteCircle(string $circleId): void {
$query = $this->connection->getQueryBuilder();
$query->delete('group_folders_groups')
->where($query->expr()->eq('circle_id', $query->createNamedParameter($circleId)));
$query->executeStatement();
$query = $this->connection->getQueryBuilder();
$query->delete('group_folders_acl')
->where($query->expr()->eq('mapping_id', $query->createNamedParameter($circleId)))
->andWhere($query->expr()->eq('mapping_type', $query->createNamedParameter('circle')));
$query->executeStatement();
}
/**
* @throws Exception
*/
public function setFolderACL(int $folderId, bool $acl): void {
$query = $this->connection->getQueryBuilder();
$query->update('group_folders')
->set('acl', $query->createNamedParameter((int)$acl, IQueryBuilder::PARAM_INT))
->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId)));
$query->executeStatement();
if ($acl === false) {
$query = $this->connection->getQueryBuilder();
$query->delete('group_folders_manage')
->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId)));
$query->executeStatement();
}
$action = $acl ? 'enabled' : 'disabled';
$this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('Advanced permissions for the groupfolder with id %d was %s', [$folderId, $action]));
}
/**
* @return list<FolderDefinitionWithPermissions>
* @throws Exception
*/
public function getFoldersForUser(IUser $user, ?int $folderId = null): array {
$groups = $this->groupManager->getUserGroupIds($user);
/** @var list<FolderDefinitionWithPermissions> $folders */
$folders = array_merge(
$this->getFoldersForGroups($groups, $folderId),
$this->getFoldersFromCircleMemberships($user, $folderId),
);
/** @var array<int, FolderDefinitionWithPermissions> $mergedFolders */
$mergedFolders = [];
foreach ($folders as $folder) {
$id = $folder->id;
if (isset($mergedFolders[$id])) {
$mergedFolders[$id] = $mergedFolders[$id]->withAddedPermissions($folder->permissions);
} else {
$mergedFolders[$id] = $folder;
}
}
return array_values($mergedFolders);
}
/**
* @throws Exception
*/
public function getFolderPermissionsForUser(IUser $user, int $folderId): int {
$groups = $this->groupManager->getUserGroupIds($user);
/** @var list<FolderDefinitionWithPermissions> $folders */
$folders = array_merge(
$this->getFoldersForGroups($groups, $folderId),
$this->getFoldersFromCircleMemberships($user, $folderId),
);
$permissions = 0;
foreach ($folders as $folder) {
if ($folderId === $folder->id) {
$permissions |= $folder->permissions;
}
}
return $permissions;
}
/**
* returns if the groupId is in fact the singleId of an existing Circle
*/
public function isACircle(string $groupId): bool {
return ($this->getCircle($groupId) !== null);
}
/**
* returns the Circle from its single Id, or NULL if not available
*/
public function getCircle(string $groupId): ?Circle {
$circlesManager = $this->getCirclesManager();
if ($circlesManager === null) {
return null;
}
$circlesManager->startSuperSession();
$probe = new CircleProbe();
$probe->includeSystemCircles();
$probe->includeSingleCircles();
try {
return $circlesManager->getCircle($groupId, $probe);
} catch (CircleNotFoundException) {
} catch (\Exception $e) {
$this->logger->warning('', ['exception' => $e]);
} finally {
$circlesManager->stopSession();
}
return null;
}
public function getCirclesManager(): ?CirclesManager {
try {
return Server::get(CirclesManager::class);
} catch (ContainerExceptionInterface|AutoloadNotAllowedException) {
return null;
}
}
private function getRealQuota(int $quota): int {
if ($quota === self::SPACE_DEFAULT) {
$defaultQuota = $this->config->getSystemValueInt('groupfolders.quota.default', FileInfo::SPACE_UNLIMITED);
// Prevent setting the default quota option to be the default quota value creating an unresolvable self reference
if ($defaultQuota <= 0 && $defaultQuota !== FileInfo::SPACE_UNLIMITED) {
throw new \Exception('Default Groupfolder quota value ' . $defaultQuota . ' is not allowed');
}
return $defaultQuota;
}
return $quota;
}
}