Skip to content

Commit ff8db14

Browse files
committed
chore(refactor): Introduce IContext and FileContext
Use it for creating the session for now. Signed-off-by: Max <max@nextcloud.com>
1 parent b1645f9 commit ff8db14

9 files changed

Lines changed: 267 additions & 53 deletions

File tree

lib/Context/FileContext.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
namespace OCA\Text\Context;
9+
10+
use OCA\Text\Db\Document;
11+
use OCA\Text\Service\FileService;
12+
use OCA\Text\Service\LockService;
13+
use OCP\Files\File;
14+
use OCP\Files\Lock\ILock;
15+
use OCP\IL10N;
16+
use OCP\IUser;
17+
18+
class FileContext implements IContext {
19+
20+
public function __construct(
21+
private readonly FileService $fileService,
22+
private readonly IL10N $l10n,
23+
private readonly LockService $lockService,
24+
private readonly File $file,
25+
private readonly ?string $baseVersionEtag,
26+
private readonly ?string $token = null,
27+
) {
28+
}
29+
30+
public function check(): ?string {
31+
// Block using text for disabled download internal shares
32+
if ($this->fileService->isDownloadDisabled($this->file)) {
33+
return $this->l10n->t('This file cannot be displayed as download is disabled by the share');
34+
}
35+
return null;
36+
}
37+
38+
public function checkDocument(Document $document): ?string {
39+
if ($this->baseVersionEtag !== null && $this->baseVersionEtag !== $document->getBaseVersionEtag()) {
40+
return $this->l10n->t('Editing session has expired. Please reload the page.');
41+
}
42+
return null;
43+
}
44+
45+
public function isReadOnly(): bool {
46+
return $this->fileService->isReadOnly($this->file, $this->token);
47+
}
48+
49+
public function getId(): int {
50+
return $this->file->getId();
51+
}
52+
53+
public function getType(): string {
54+
return 'file';
55+
}
56+
57+
public function toString(): string {
58+
return $this->getType() . ' (' . $this->getId() . ')';
59+
}
60+
61+
public function loadContent(): ?string {
62+
return $this->fileService->loadContent($this->file);
63+
}
64+
65+
public function getLockInfo(): ?ILock {
66+
return $this->lockService->getLockByOthers($this->file);
67+
}
68+
69+
public function getOwner(): ?IUser {
70+
return $this->file->getOwner();
71+
}
72+
73+
public function lock(): bool {
74+
// Disable file locking for Readme.md files, because in the
75+
// current setup, this makes it almost impossible to delete these files.
76+
if (strcasecmp($this->file->getName(), 'Readme.md') !== 0) {
77+
return $this->lockService->lock($this->file);
78+
}
79+
return true;
80+
}
81+
82+
public function createDocument(): Document {
83+
$document = new Document();
84+
$document->setId($this->getId());
85+
$document->setLastSavedVersion(0);
86+
$document->setLastSavedVersionTime($this->file->getMTime());
87+
$document->setLastSavedVersionEtag($this->file->getEtag());
88+
$document->setChecksum($this->computeChecksum());
89+
// This is a new document - so it needs a fresh base version etag.
90+
$document->setBaseVersionEtag(uniqid());
91+
return $document;
92+
}
93+
94+
public function computeCheckSum(): string {
95+
return hash('crc32', $this->file->getContent());
96+
}
97+
98+
}

lib/Context/FileContextFactory.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
namespace OCA\Text\Context;
9+
10+
use OCA\Text\Service\FileService;
11+
use OCA\Text\Service\LockService;
12+
use OCP\Constants;
13+
use OCP\DirectEditing\IToken;
14+
use OCP\Files\NotFoundException;
15+
use OCP\Files\NotPermittedException;
16+
use OCP\IL10N;
17+
use OCP\IUserSession;
18+
19+
class FileContextFactory {
20+
21+
public function __construct(
22+
private readonly FileService $fileService,
23+
private readonly IL10N $l10n,
24+
private readonly LockService $lockService,
25+
private readonly IUserSession $userSession,
26+
) {
27+
}
28+
29+
/**
30+
* @throws NotPermittedException if not logged in
31+
* @throws NotFoundException if the file cannot be found
32+
*/
33+
public function buildForId(
34+
int $id,
35+
?string $baseVersionEtag,
36+
): FileContext {
37+
$userId = $this->userSession->getUser()?->getUID();
38+
if ($userId === null) {
39+
throw new NotPermittedException();
40+
}
41+
$file = $this->fileService->getFileById($id, $userId);
42+
return new FileContext(
43+
$this->fileService,
44+
$this->l10n,
45+
$this->lockService,
46+
$file,
47+
$baseVersionEtag,
48+
);
49+
}
50+
51+
/**
52+
* @throws NotPermittedException if not logged in
53+
* @throws NotFoundException if the file cannot be found
54+
*/
55+
public function buildForShareWithPath(
56+
string $token,
57+
?string $filePath,
58+
?string $baseVersionEtag,
59+
): FileContext {
60+
$file = $this->fileService->getFileByShareToken($token, $filePath);
61+
$this->fileService->checkSharePermissions($token, Constants::PERMISSION_READ);
62+
return new FileContext(
63+
$this->fileService,
64+
$this->l10n,
65+
$this->lockService,
66+
$file,
67+
$baseVersionEtag,
68+
$token,
69+
);
70+
}
71+
72+
/**
73+
* @throws NotFoundException if the file cannot be found
74+
*/
75+
public function buildForDirectEditing(IToken $token): FileContext {
76+
$file = $token->getFile();
77+
return new FileContext(
78+
$this->fileService,
79+
$this->l10n,
80+
$this->lockService,
81+
$file,
82+
null,
83+
);
84+
}
85+
86+
}

lib/Context/IContext.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
namespace OCA\Text\Context;
9+
10+
use OCA\Text\Db\Document;
11+
use OCP\Files\Lock\ILock;
12+
use OCP\IUser;
13+
14+
interface IContext {
15+
public function check(): ?string;
16+
public function checkDocument(Document $document): ?string;
17+
public function isReadOnly(): bool;
18+
public function getId(): int;
19+
public function getType(): string;
20+
public function toString(): string;
21+
public function loadContent(): ?string;
22+
public function getLockInfo(): ?ILock;
23+
public function getOwner(): ?IUser;
24+
public function lock(): bool;
25+
public function createDocument(): Document;
26+
}

lib/Controller/PublicSessionController.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace OCA\Text\Controller;
1010

11+
use OCA\Text\Context\FileContextFactory;
1112
use OCA\Text\Middleware\Attribute\RequireDocumentBaseVersionEtag;
1213
use OCA\Text\Middleware\Attribute\RequireDocumentSession;
1314
use OCA\Text\Service\ApiService;
@@ -17,7 +18,6 @@
1718
use OCP\AppFramework\Http\Attribute\PublicPage;
1819
use OCP\AppFramework\Http\DataResponse;
1920
use OCP\AppFramework\PublicShareController;
20-
use OCP\Constants;
2121
use OCP\Files\NotFoundException;
2222
use OCP\Files\NotPermittedException;
2323
use OCP\IL10N;
@@ -36,6 +36,7 @@ public function __construct(
3636
string $appName,
3737
IRequest $request,
3838
ISession $session,
39+
private FileContextFactory $fileContextFactory,
3940
private ShareManager $shareManager,
4041
private ApiService $apiService,
4142
private FileService $fileService,
@@ -73,20 +74,19 @@ protected function isPasswordProtected(): bool {
7374
#[NoAdminRequired]
7475
#[PublicPage]
7576
public function create(string $token, ?string $filePath = null, ?string $baseVersionEtag = null, ?string $guestName = null): DataResponse {
76-
$file = $this->fileService->getFileByShareToken($token, $filePath);
7777
/*
7878
* Check if we have proper read access (files drop)
7979
* If not then well 404 it is.
8080
*/
8181
try {
82-
$this->fileService->checkSharePermissions($token, Constants::PERMISSION_READ);
82+
$context = $this->fileContextFactory->buildForShareWithPath($token, $filePath, $baseVersionEtag);
83+
return $this->apiService->create($context, $guestName);
8384
} catch (NotFoundException) {
8485
return new DataResponse([], Http::STATUS_NOT_FOUND);
8586
} catch (NotPermittedException) {
8687
return new DataResponse(['error' => $this->l10n->t('This file cannot be displayed as download is disabled by the share')], Http::STATUS_NOT_FOUND);
8788
}
8889

89-
return $this->apiService->create($file, $baseVersionEtag, $token, $guestName);
9090
}
9191

9292
#[NoAdminRequired]

lib/Controller/SessionController.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace OCA\Text\Controller;
1010

11+
use OCA\Text\Context\FileContextFactory;
1112
use OCA\Text\Exception\InvalidSessionException;
1213
use OCA\Text\Middleware\Attribute\RequireDocumentBaseVersionEtag;
1314
use OCA\Text\Middleware\Attribute\RequireDocumentSession;
@@ -40,6 +41,7 @@ public function __construct(
4041
string $appName,
4142
IRequest $request,
4243
private ApiService $apiService,
44+
private FileContextFactory $fileContextFactory,
4345
private FileService $fileService,
4446
private SessionService $sessionService,
4547
private NotificationService $notificationService,
@@ -53,21 +55,20 @@ public function __construct(
5355

5456
#[NoAdminRequired]
5557
public function create(?int $fileId = null, ?string $baseVersionEtag = null): DataResponse {
56-
$userId = $this->userSession->getUser()?->getUID();
57-
if ($fileId === null || $userId === null) {
58+
if ($fileId === null) {
5859
return new DataResponse(['error' => 'No valid file argument provided'], Http::STATUS_PRECONDITION_FAILED);
5960
}
6061

6162
try {
62-
$file = $this->fileService->getFileById($fileId, $userId);
63+
$context = $this->fileContextFactory->buildForId($fileId, $baseVersionEtag);
6364
} catch (NotFoundException|NotPermittedException $e) {
6465
$this->logger->error('No permission to access this file', [ 'exception' => $e ]);
6566
return new DataResponse([
6667
'error' => $this->l10n->t('File not found')
6768
], Http::STATUS_NOT_FOUND);
6869
}
6970

70-
return $this->apiService->create($file, $baseVersionEtag);
71+
return $this->apiService->create($context);
7172
}
7273

7374
#[NoAdminRequired]

lib/DirectEditing/TextDirectEditor.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace OCA\Text\DirectEditing;
99

1010
use OCA\Text\AppInfo\Application;
11+
use OCA\Text\Context\FileContextFactory;
1112
use OCA\Text\Service\ApiService;
1213
use OCA\Text\Service\InitialStateProvider;
1314
use OCP\AppFramework\Http\NotFoundResponse;
@@ -29,6 +30,7 @@ public function __construct(
2930
private readonly InitialStateProvider $initialStateProvider,
3031
private readonly ApiService $apiService,
3132
private readonly IAppConfig $appConfig,
33+
private readonly FileContextFactory $fileContextFactory,
3234
) {
3335
}
3436

@@ -131,7 +133,8 @@ public function isSecure(): bool {
131133
public function open(IToken $token): Response {
132134
$token->useTokenScope();
133135
try {
134-
$session = $this->apiService->create($token->getFile());
136+
$context = $this->fileContextFactory->buildForDirectEditing($token);
137+
$session = $this->apiService->create($context);
135138
$this->initialStateProvider->provideFile([
136139
'fileId' => $token->getFile()->getId(),
137140
'mimetype' => $token->getFile()->getMimeType(),

0 commit comments

Comments
 (0)