From 614981ae9abbfed04b9d1b3663f7e9d0aa85404c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Wed, 15 Feb 2023 16:16:54 +0100 Subject: [PATCH] feat(directediting): Allow opening by file id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- .../Controller/DirectEditingController.php | 5 +- apps/files/lib/DirectEditingCapabilities.php | 5 +- lib/private/DirectEditing/Manager.php | 25 +++++- tests/lib/DirectEditing/ManagerTest.php | 80 +++++++++++++++++++ 4 files changed, 106 insertions(+), 9 deletions(-) diff --git a/apps/files/lib/Controller/DirectEditingController.php b/apps/files/lib/Controller/DirectEditingController.php index c67150be8d4..9b48d6958aa 100644 --- a/apps/files/lib/Controller/DirectEditingController.php +++ b/apps/files/lib/Controller/DirectEditingController.php @@ -35,7 +35,6 @@ use OCP\IRequest; use OCP\IURLGenerator; class DirectEditingController extends OCSController { - /** @var IEventDispatcher */ private $eventDispatcher; @@ -94,14 +93,14 @@ class DirectEditingController extends OCSController { /** * @NoAdminRequired */ - public function open(string $path, string $editorId = null): DataResponse { + public function open(string $path, string $editorId = null, ?int $fileId = null): DataResponse { if (!$this->directEditingManager->isEnabled()) { return new DataResponse(['message' => 'Direct editing is not enabled'], Http::STATUS_INTERNAL_SERVER_ERROR); } $this->eventDispatcher->dispatchTyped(new RegisterDirectEditorEvent($this->directEditingManager)); try { - $token = $this->directEditingManager->open($path, $editorId); + $token = $this->directEditingManager->open($path, $editorId, $fileId); return new DataResponse([ 'url' => $this->urlGenerator->linkToRouteAbsolute('files.DirectEditingView.edit', ['token' => $token]) ]); diff --git a/apps/files/lib/DirectEditingCapabilities.php b/apps/files/lib/DirectEditingCapabilities.php index 782f7019ac7..10c8e95105a 100644 --- a/apps/files/lib/DirectEditingCapabilities.php +++ b/apps/files/lib/DirectEditingCapabilities.php @@ -1,4 +1,5 @@ @@ -29,7 +30,6 @@ use OCP\Capabilities\IInitialStateExcludedCapability; use OCP\IURLGenerator; class DirectEditingCapabilities implements ICapability, IInitialStateExcludedCapability { - protected DirectEditingService $directEditingService; protected IURLGenerator $urlGenerator; @@ -43,7 +43,8 @@ class DirectEditingCapabilities implements ICapability, IInitialStateExcludedCap 'files' => [ 'directEditing' => [ 'url' => $this->urlGenerator->linkToOCSRouteAbsolute('files.DirectEditing.info'), - 'etag' => $this->directEditingService->getDirectEditingETag() + 'etag' => $this->directEditingService->getDirectEditingETag(), + 'supportsFileId' => true, ] ], ]; diff --git a/lib/private/DirectEditing/Manager.php b/lib/private/DirectEditing/Manager.php index 039944e2491..2dd2abe5408 100644 --- a/lib/private/DirectEditing/Manager.php +++ b/lib/private/DirectEditing/Manager.php @@ -26,10 +26,11 @@ namespace OC\DirectEditing; use Doctrine\DBAL\FetchMode; -use OC\Files\Node\Folder; +use \OCP\Files\Folder; use OCP\AppFramework\Http\NotFoundResponse; use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\TemplateResponse; +use OCP\Constants; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DirectEditing\ACreateFromTemplate; use OCP\DirectEditing\IEditor; @@ -152,9 +153,25 @@ class Manager implements IManager { throw new \RuntimeException('No creator found'); } - public function open(string $filePath, string $editorId = null): string { - /** @var File $file */ - $file = $this->rootFolder->getUserFolder($this->userId)->get($filePath); + public function open(string $filePath, string $editorId = null, ?int $fileId = null): string { + $userFolder = $this->rootFolder->getUserFolder($this->userId); + $file = $userFolder->get($filePath); + if ($fileId !== null && $file instanceof Folder) { + $files = $file->getById($fileId); + + // Workaround to always open files with edit permissions if multiple occurences of + // the same file id are in the user home, ideally we should also track the path of the file when opening + usort($files, function (Node $a, Node $b) { + return ($b->getPermissions() & Constants::PERMISSION_UPDATE) <=> ($a->getPermissions() & Constants::PERMISSION_UPDATE); + }); + $file = array_shift($files); + } + + if (!$file instanceof File) { + throw new NotFoundException(); + } + + $filePath = $userFolder->getRelativePath($file->getPath()); if ($editorId === null) { $editorId = $this->findEditorForFile($file); diff --git a/tests/lib/DirectEditing/ManagerTest.php b/tests/lib/DirectEditing/ManagerTest.php index 7a2f2e3d772..53588093092 100644 --- a/tests/lib/DirectEditing/ManagerTest.php +++ b/tests/lib/DirectEditing/ManagerTest.php @@ -211,6 +211,86 @@ class ManagerTest extends TestCase { $this->assertInstanceOf(NotFoundResponse::class, $secondResult); } + public function testOpenByPath() { + $expectedToken = 'TOKEN' . time(); + $file = $this->createMock(File::class); + $file->expects($this->any()) + ->method('getId') + ->willReturn(123); + $file->expects($this->any()) + ->method('getPath') + ->willReturn('/admin/files/File.txt'); + $this->random->expects($this->once()) + ->method('generate') + ->willReturn($expectedToken); + $folder = $this->createMock(Folder::class); + $this->userFolder + ->method('nodeExists') + ->withConsecutive(['/File.txt'], ['/']) + ->willReturnOnConsecutiveCalls(false, true); + $this->userFolder + ->method('get') + ->with('/File.txt') + ->willReturn($file); + $this->userFolder + ->method('getRelativePath') + ->willReturn('/File.txt'); + $this->manager->open('/File.txt', 'testeditor'); + $firstResult = $this->manager->edit($expectedToken); + $secondResult = $this->manager->edit($expectedToken); + $this->assertInstanceOf(DataResponse::class, $firstResult); + $this->assertInstanceOf(NotFoundResponse::class, $secondResult); + } + + public function testOpenById() { + $expectedToken = 'TOKEN' . time(); + $fileRead = $this->createMock(File::class); + $fileRead->method('getPermissions') + ->willReturn(1); + $fileRead->expects($this->any()) + ->method('getId') + ->willReturn(123); + $fileRead->expects($this->any()) + ->method('getPath') + ->willReturn('/admin/files/shared_file.txt'); + $file = $this->createMock(File::class); + $file->method('getPermissions') + ->willReturn(1); + $file->expects($this->any()) + ->method('getId') + ->willReturn(123); + $file->expects($this->any()) + ->method('getPath') + ->willReturn('/admin/files/File.txt'); + $this->random->expects($this->once()) + ->method('generate') + ->willReturn($expectedToken); + $folder = $this->createMock(Folder::class); + $folder->expects($this->any()) + ->method('getById') + ->willReturn([ + $fileRead, + $file + ]); + $this->userFolder + ->method('nodeExists') + ->withConsecutive(['/File.txt'], ['/']) + ->willReturnOnConsecutiveCalls(false, true); + $this->userFolder + ->method('get') + ->with('/') + ->willReturn($folder); + $this->userFolder + ->method('getRelativePath') + ->willReturn('/File.txt'); + + $this->manager->open('/', 'testeditor', 123); + $firstResult = $this->manager->edit($expectedToken); + $secondResult = $this->manager->edit($expectedToken); + $this->assertInstanceOf(DataResponse::class, $firstResult); + $this->assertInstanceOf(NotFoundResponse::class, $secondResult); + } + public function testCreateFileAlreadyExists() { $this->expectException(\RuntimeException::class); $this->userFolder