diff --git a/cypress/e2e/attachments.spec.js b/cypress/e2e/attachments.spec.js old mode 100755 new mode 100644 index 4f80fb139b2..d080ffb87cd --- a/cypress/e2e/attachments.spec.js +++ b/cypress/e2e/attachments.spec.js @@ -447,67 +447,4 @@ describe('Test all attachment insertion methods', () => { cy.getFile('.attachments.' + documentId).should('not.exist') }) }) - - it('[share] check everything behaves correctly on the share target user side', () => { - const fileName = 'testShared.md' - cy.createMarkdown( - fileName, - '![git](.attachments.123/github.png)', - false, - ).then((fileId) => { - const attachmentsFolder = `.attachments.${fileId}` - cy.createFolder(attachmentsFolder) - cy.uploadFile( - 'github.png', - 'image/png', - `${attachmentsFolder}/github.png`, - ) - cy.shareFileToUser(fileName, recipient) - }) - - cy.login(recipient) - cy.showHiddenFiles() - - cy.visit('/apps/files') - // check the file list - cy.getFile('testShared.md').should('exist') - cy.getFile('github.png').should('not.exist') - - // check the attachment folder is not there - cy.getFile('testShared.md') - .should('exist') - .should('have.attr', 'data-cy-files-list-row-fileid') - .then((documentId) => { - cy.getFile('.attachments.' + documentId).should('not.exist') - }) - - // move the file and check the attachment folder is still not there - cy.moveFile('testShared.md', 'testMoved.md') - cy.reloadFileList() - cy.getFile('testMoved.md') - .should('exist') - .should('have.attr', 'data-cy-files-list-row-fileid') - .then((documentId) => { - cy.getFile('.attachments.' + documentId).should('not.exist') - }) - - // copy the file and check the attachment folder was copied - cy.copyFile('testMoved.md', 'testCopied.md') - cy.reloadFileList() - cy.getFile('testCopied.md') - .should('exist') - .should('have.attr', 'data-cy-files-list-row-fileid') - .then((documentId) => { - const files = attachmentFileNameToId[documentId] - cy.openFolder('.attachments.' + documentId) - for (const name in files) { - cy.getFile(name) - .should('exist') - .should('have.attr', 'data-cy-files-list-row-fileid') - // these are new copied attachment files - // so they should not have the same IDs than the ones created when uploading the files - .should('not.eq', String(files[name])) - } - }) - }) }) diff --git a/cypress/e2e/shareWithAttachments.spec.js b/cypress/e2e/shareWithAttachments.spec.js new file mode 100644 index 00000000000..5d5b13127cf --- /dev/null +++ b/cypress/e2e/shareWithAttachments.spec.js @@ -0,0 +1,100 @@ +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { randUser } from '../utils/index.js' + +const user = randUser() +const recipient = randUser() + +describe('Share with attachments', () => { + before(() => { + cy.createUser(user) + cy.createUser(recipient) + }) + + it('handle file operations by recipient user', () => { + cy.login(user) + const fileName = 'testShared.md' + cy.createFile(fileName, '![git](.attachments.123/github.png)') + .as('textFileId') + .then((fileId) => { + const attachmentsFolder = `.attachments.${fileId}` + cy.createFolder(attachmentsFolder) + cy.uploadFile( + 'github.png', + 'image/png', + `${attachmentsFolder}/github.png`, + ).as('attachmentId') + cy.shareFileToUser(fileName, recipient) + }) + + cy.login(recipient) + cy.showHiddenFiles() + + cy.visit('/apps/files') + // check the file list + cy.getFile('testShared.md').should('exist') + cy.getFile('github.png').should('not.exist') + + // check the attachment folder is not there + cy.getFileId('testShared.md').then((documentId) => { + cy.getFile('.attachments.' + documentId).should('not.exist') + }) + + // move the file and check the attachment folder is still not there + cy.moveFile('testShared.md', 'testMoved.md') + cy.reloadFileList() + cy.getFileId('testMoved.md').then((documentId) => { + cy.getFile('.attachments.' + documentId).should('not.exist') + }) + + // copy the file and check the attachment folder was copied + cy.copyFile('testMoved.md', 'testCopied.md') + cy.reloadFileList() + cy.getFileId('testCopied.md').then((documentId) => { + cy.openFolder('.attachments.' + documentId) + }) + cy.get('@attachmentId').then((attachmentId) => { + // these are new copied attachment files + // so they should not have the same IDs than the ones created when uploading the files + cy.getFileId('github.png').should('not.eq', String(attachmentId)) + }) + }) +}) + +describe('Public Share with attachments', () => { + before(function () { + cy.createUser(user) + }) + + beforeEach(function () { + cy.login(user) + cy.createTestFolder().as('folder').then(cy.shareFile).as('token') + cy.then(function () { + cy.createFile( + `${this.folder}/Readme.md`, + '![Attached text](.attachments.123/lines.txt)', + ).as('fileId') + }) + cy.then(function () { + const attachmentsFolder = `${this.folder}/.attachments.${this.fileId}` + cy.createFolder(attachmentsFolder) + cy.uploadFile( + 'lines.txt', + 'text/plain', + `${attachmentsFolder}/lines.txt`, + ) + }) + cy.clearCookies() + }) + + it('open attached files in folder description', function () { + cy.visit(`/s/${this.token}`) + cy.get('.content-wrapper').should('exist') + cy.get('.content-wrapper .name', { timeout: 10_000 }).click() + cy.get('.viewer').should('exist') + cy.get('.language-plaintext').should('contain', 'multiple lines') + }) +}) diff --git a/lib/Service/AttachmentService.php b/lib/Service/AttachmentService.php index 18f3e8b32a8..fc5e0214fa7 100755 --- a/lib/Service/AttachmentService.php +++ b/lib/Service/AttachmentService.php @@ -220,7 +220,11 @@ public function getAttachmentList(int $documentId, ?string $userId = null, ?Sess : '?documentId=' . $documentId . $shareTokenUrlString; $attachments = []; - $userFolder = $userId !== null ? $this->rootFolder->getUserFolder($userId) : null; + + // Folder davPath need to be relative to. + $davFolder = $userId !== null + ? $this->rootFolder->getUserFolder($userId) + : $this->getShareFolder($shareToken); $fileNodes = []; $fileIds = []; @@ -247,7 +251,7 @@ public function getAttachmentList(int $documentId, ?string $userId = null, ?Sess 'mimetype' => $node->getMimeType(), 'mtime' => $node->getMTime(), 'isImage' => $isImage, - 'davPath' => $userFolder?->getRelativePath($node->getPath()), + 'davPath' => $davFolder?->getRelativePath($node->getPath()), 'metadata' => $metadata, 'fullUrl' => $isImage ? $this->urlGenerator->linkToRouteAbsolute('text.Attachment.getImageFile') . $urlParamsBase . '&imageFileName=' . rawurlencode($name) . '&preferRawImage=1' @@ -574,6 +578,35 @@ private function getTextFilePublic(?int $documentId, string $shareToken): File { throw new NotFoundException('Text file with id=' . $documentId . ' and shareToken ' . $shareToken . ' was not found.'); } + /** + * Get share folder + * + * @param string $shareToken + * + * @throws NotFoundException + */ + private function getShareFolder(string $shareToken): ?Folder { + // is the file shared with this token? + try { + $share = $this->shareManager->getShareByToken($shareToken); + if (in_array($share->getShareType(), [IShare::TYPE_LINK, IShare::TYPE_EMAIL])) { + // shared file or folder? + if ($share->getNodeType() === 'file') { + return null; + } elseif ($share->getNodeType() === 'folder') { + $folder = $share->getNode(); + if ($folder instanceof Folder) { + return $folder; + } + throw new NotFoundException('Share folder for ' . $shareToken . ' was not a folder.'); + } + } + } catch (ShareNotFound $e) { + // same as below + } + throw new NotFoundException('Share folder for ' . $shareToken . ' was not found.'); + } + /** * Actually delete attachment files which are not pointed in the markdown content *