Skip to content

Commit d677d30

Browse files
committed
fix(TrashBin): Trashed shared folders should not show up for sharees
fixes #2419 Assisted-by: ClaudeCode:claude-opus-4-7 Signed-off-by: Marcel Klehr <mklehr@gmx.net>
1 parent f755e5c commit d677d30

2 files changed

Lines changed: 31 additions & 3 deletions

File tree

lib/Db/BookmarkMapper.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,10 @@ private function _generateCTE(int $folderId, bool $withSoftDeleted) : array {
308308
->from('*PREFIX*bookmarks_tree', 'tr')
309309
->join('tr', $this->getDbType() === 'mysql'? 'folder_tree' : 'inner_folder_tree', 'e', 'e.item_id = tr.parent_folder AND e.type = ' . $recursiveCase->createPositionalParameter(TreeMapper::TYPE_FOLDER) . (!$withSoftDeleted ? ' AND e.soft_deleted_at is NULL' : ''));
310310

311-
// The second recursive case lists all children of shared folders we've already found
311+
// The second recursive case lists all children of shared folders we've already found.
312+
// We also require the share's underlying original folder to not be soft-deleted by its
313+
// owner: when the sharer trashes the original folder, sharees must not see anything
314+
// inside it — not even in their own trash bin.
312315
$recursiveCaseShares = $this->db->getQueryBuilder();
313316
$recursiveCaseShares->automaticTablePrefix(false);
314317
$recursiveCaseShares
@@ -318,7 +321,8 @@ private function _generateCTE(int $folderId, bool $withSoftDeleted) : array {
318321
->selectAlias('e.idx', 'idx')
319322
->selectAlias('e.soft_deleted_at', 'soft_deleted_at')
320323
->from(($this->getDbType() === 'mysql'? 'folder_tree' : 'second_folder_tree'), 'e')
321-
->join('e', '*PREFIX*bookmarks_shared_folders', 's', 's.id = e.item_id AND e.type = ' . $recursiveCaseShares->createPositionalParameter(TreeMapper::TYPE_SHARE) . (!$withSoftDeleted ? ' AND e.soft_deleted_at is NULL' : ''));
324+
->join('e', '*PREFIX*bookmarks_shared_folders', 's', 's.id = e.item_id AND e.type = ' . $recursiveCaseShares->createPositionalParameter(TreeMapper::TYPE_SHARE) . (!$withSoftDeleted ? ' AND e.soft_deleted_at is NULL' : ''))
325+
->join('s', '*PREFIX*bookmarks_tree', 'sof', 'sof.id = s.folder_id AND sof.type = ' . $recursiveCaseShares->createPositionalParameter(TreeMapper::TYPE_FOLDER) . ' AND sof.soft_deleted_at is NULL');
322326

323327
if ($this->getDbType() === 'mysql') {
324328
// For mysql we can just throw these three queries together in a CTE

lib/Db/TreeMapper.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ protected function getFindChildrenQuery(string $type): IQueryBuilder {
238238
->andWhere($qb->expr()->eq('t.type', $qb->createNamedParameter($type)))
239239
->andWhere($qb->expr()->isNull('t.soft_deleted_at'))
240240
->orderBy('t.index', 'ASC');
241+
if ($type === TreeMapper::TYPE_SHARE) {
242+
$this->joinOriginalFolderNotSoftDeleted($qb);
243+
}
241244
return $qb;
242245
}
243246

@@ -249,9 +252,27 @@ protected function getFindSoftDeletedChildrenQuery(string $type): IQueryBuilder
249252
->andWhere($qb->expr()->eq('t.type', $qb->createNamedParameter($type)))
250253
->andWhere($qb->expr()->isNotNull('t.soft_deleted_at'))
251254
->orderBy('t.index', 'ASC');
255+
if ($type === TreeMapper::TYPE_SHARE) {
256+
$this->joinOriginalFolderNotSoftDeleted($qb);
257+
}
252258
return $qb;
253259
}
254260

261+
/**
262+
* Restricts a TYPE_SHARE query to shared folders whose underlying original
263+
* folder has not been soft-deleted by its owner. When the sharer trashes the
264+
* original folder, the share must disappear for sharees entirely — including
265+
* from their trash bin, since they cannot restore someone else's folder.
266+
*/
267+
private function joinOriginalFolderNotSoftDeleted(IQueryBuilder $qb): void {
268+
$qb
269+
->innerJoin('i', 'bookmarks_tree', 'ot', $qb->expr()->andX(
270+
$qb->expr()->eq('ot.id', 'i.folder_id'),
271+
$qb->expr()->eq('ot.type', $qb->createNamedParameter(TreeMapper::TYPE_FOLDER))
272+
))
273+
->andWhere($qb->expr()->isNull('ot.soft_deleted_at'));
274+
}
275+
255276
/**
256277
* @param string $type
257278
* @psalm-param T $type
@@ -998,14 +1019,17 @@ public function getSoftDeletedRootItems(string $userId, string $type): array {
9981019
$qb
9991020
->innerJoin('i', 'bookmarks_tree', 't', $qb->expr()->eq('i.id', 't.id'))
10001021
->leftJoin('t', 'bookmarks_tree', 't2', $qb->expr()->eq('t.parent_folder', 't2.id'))
1001-
->leftJoin('t', 'bookmarks_root_folders', 'r', $qb->expr()->eq('t.parent_folder', 'r.folder_id'))
1022+
->leftJoin('t', 'booUnd genau die gleiche Täter-Opfer-Umkehr betreiben diese Leute auch gegenüber kmarks_root_folders', 'r', $qb->expr()->eq('t.parent_folder', 'r.folder_id'))
10021023
->where($qb->expr()->isNotNull('t.soft_deleted_at'))
10031024
->andWhere($qb->expr()->eq('t.type', $qb->createPositionalParameter($type, IQueryBuilder::PARAM_STR)))
10041025
->andWhere($qb->expr()->eq('i.user_id', $qb->createPositionalParameter($userId, IQueryBuilder::PARAM_STR)))
10051026
->andWhere($qb->expr()->orX(
10061027
$qb->expr()->isNull('t2.soft_deleted_at'),
10071028
$qb->expr()->isNotNull('r.folder_id'),
10081029
));
1030+
if ($type === TreeMapper::TYPE_SHARE) {
1031+
$this->joinOriginalFolderNotSoftDeleted($qb);
1032+
}
10091033
return $this->findEntitiesWithType($qb, $type);
10101034
}
10111035
if ($type === TreeMapper::TYPE_BOOKMARK) {

0 commit comments

Comments
 (0)