|
16 | 16 | use OCA\Richdocuments\Exceptions\UnknownTokenException;
|
17 | 17 | use OCA\Richdocuments\Helper;
|
18 | 18 | use OCA\Richdocuments\PermissionManager;
|
| 19 | +use OCA\Richdocuments\Service\CapabilitiesService; |
19 | 20 | use OCA\Richdocuments\Service\FederationService;
|
| 21 | +use OCA\Richdocuments\Service\SettingsService; |
20 | 22 | use OCA\Richdocuments\Service\UserScopeService;
|
21 | 23 | use OCA\Richdocuments\TaskProcessingManager;
|
22 | 24 | use OCA\Richdocuments\TemplateManager;
|
23 | 25 | use OCA\Richdocuments\TokenManager;
|
| 26 | +use OCA\Richdocuments\WOPI\SettingsUrl; |
24 | 27 | use OCP\AppFramework\Controller;
|
25 | 28 | use OCP\AppFramework\Http;
|
26 | 29 | use OCP\AppFramework\Http\Attribute\FrontpageRoute;
|
|
44 | 47 | use OCP\Files\Lock\OwnerLockedException;
|
45 | 48 | use OCP\Files\Node;
|
46 | 49 | use OCP\Files\NotFoundException;
|
| 50 | +use OCP\Files\NotPermittedException; |
47 | 51 | use OCP\IConfig;
|
48 | 52 | use OCP\IGroupManager;
|
49 | 53 | use OCP\IRequest;
|
@@ -86,6 +90,8 @@ public function __construct(
|
86 | 90 | private ILockManager $lockManager,
|
87 | 91 | private IEventDispatcher $eventDispatcher,
|
88 | 92 | private TaskProcessingManager $taskProcessingManager,
|
| 93 | + private SettingsService $settingsService, |
| 94 | + private CapabilitiesService $capabilitiesService, |
89 | 95 | ) {
|
90 | 96 | parent::__construct($appName, $request);
|
91 | 97 | }
|
@@ -133,11 +139,14 @@ public function checkFileInfo(string $fileId, string $access_token): JSONRespons
|
133 | 139 | } catch (NoLockProviderException|PreConditionNotMetException) {
|
134 | 140 | }
|
135 | 141 |
|
| 142 | + $userId = !$isPublic ? $wopi->getEditorUid() : $guestUserId; |
| 143 | + |
| 144 | + |
136 | 145 | $response = [
|
137 | 146 | 'BaseFileName' => $file->getName(),
|
138 | 147 | 'Size' => $file->getSize(),
|
139 | 148 | 'Version' => $version,
|
140 |
| - 'UserId' => !$isPublic ? $wopi->getEditorUid() : $guestUserId, |
| 149 | + 'UserId' => $userId, |
141 | 150 | 'OwnerId' => $wopi->getOwnerUid(),
|
142 | 151 | 'UserFriendlyName' => $userDisplayName,
|
143 | 152 | 'UserExtraInfo' => [],
|
@@ -167,6 +176,14 @@ public function checkFileInfo(string $fileId, string $access_token): JSONRespons
|
167 | 176 | 'ServerPrivateInfo' => [],
|
168 | 177 | ];
|
169 | 178 |
|
| 179 | + if ($this->capabilitiesService->hasSettingIframeSupport()) { |
| 180 | + |
| 181 | + if (!$isPublic) { |
| 182 | + $response['UserSettings'] = $this->generateSettings($userId, 'userconfig'); |
| 183 | + } |
| 184 | + $response['SharedSettings'] = $this->generateSettings($userId, 'systemconfig'); |
| 185 | + } |
| 186 | + |
170 | 187 | $enableZotero = $this->config->getAppValue(Application::APPNAME, 'zoteroEnabled', 'yes') === 'yes';
|
171 | 188 | if (!$isPublic && $enableZotero) {
|
172 | 189 | $zoteroAPIKey = $this->config->getUserValue($wopi->getEditorUid(), 'richdocuments', 'zoteroAPIKey', '');
|
@@ -381,6 +398,111 @@ public function getFile(string $fileId, string $access_token): JSONResponse|Stre
|
381 | 398 | }
|
382 | 399 | }
|
383 | 400 |
|
| 401 | + #[NoAdminRequired] |
| 402 | + #[NoCSRFRequired] |
| 403 | + #[PublicPage] |
| 404 | + #[FrontpageRoute(verb: 'GET', url: 'wopi/settings')] |
| 405 | + public function getSettings(string $type, string $access_token): JSONResponse { |
| 406 | + if (empty($type)) { |
| 407 | + return new JSONResponse(['error' => 'Invalid type parameter'], Http::STATUS_BAD_REQUEST); |
| 408 | + } |
| 409 | + |
| 410 | + try { |
| 411 | + $wopi = $this->wopiMapper->getWopiForToken($access_token); |
| 412 | + if ($wopi->getTokenType() !== Wopi::TOKEN_TYPE_SETTING_AUTH) { |
| 413 | + return new JSONResponse(['error' => 'Invalid token type'], Http::STATUS_BAD_REQUEST); |
| 414 | + } |
| 415 | + |
| 416 | + $isPublic = empty($wopi->getEditorUid()); |
| 417 | + $guestUserId = 'Guest-' . \OC::$server->getSecureRandom()->generate(8); |
| 418 | + $userId = !$isPublic ? $wopi->getEditorUid() : $guestUserId; |
| 419 | + |
| 420 | + $userConfig = $this->settingsService->generateSettingsConfig($type, $userId); |
| 421 | + return new JSONResponse($userConfig, Http::STATUS_OK); |
| 422 | + } catch (UnknownTokenException|ExpiredTokenException $e) { |
| 423 | + $this->logger->debug($e->getMessage(), ['exception' => $e]); |
| 424 | + return new JSONResponse(['error' => 'Unauthorized'], Http::STATUS_UNAUTHORIZED); |
| 425 | + } catch (\Exception $e) { |
| 426 | + $this->logger->error($e->getMessage(), ['exception' => $e]); |
| 427 | + return new JSONResponse(['error' => 'Internal Server Error'], Http::STATUS_INTERNAL_SERVER_ERROR); |
| 428 | + } |
| 429 | + } |
| 430 | + |
| 431 | + #[NoAdminRequired] |
| 432 | + #[NoCSRFRequired] |
| 433 | + #[PublicPage] |
| 434 | + #[FrontpageRoute(verb: 'POST', url: 'wopi/settings/upload')] |
| 435 | + public function uploadSettingsFile(string $fileId, string $access_token): JSONResponse { |
| 436 | + try { |
| 437 | + $wopi = $this->wopiMapper->getWopiForToken($access_token); |
| 438 | + |
| 439 | + $userId = $wopi->getEditorUid(); |
| 440 | + |
| 441 | + $content = fopen('php://input', 'rb'); |
| 442 | + if (!$content) { |
| 443 | + throw new \Exception('Failed to read input stream.'); |
| 444 | + } |
| 445 | + |
| 446 | + $fileContent = stream_get_contents($content); |
| 447 | + fclose($content); |
| 448 | + |
| 449 | + // Use the fileId as a file path URL (e.g., "/settings/systemconfig/wordbook/en_US%20(1).dic") |
| 450 | + $settingsUrl = new SettingsUrl($fileId); |
| 451 | + $result = $this->settingsService->uploadFile($settingsUrl, $fileContent, $userId); |
| 452 | + |
| 453 | + return new JSONResponse([ |
| 454 | + 'status' => 'success', |
| 455 | + 'filename' => $settingsUrl->getFileName(), |
| 456 | + 'details' => $result, |
| 457 | + ], Http::STATUS_OK); |
| 458 | + |
| 459 | + } catch (UnknownTokenException $e) { |
| 460 | + $this->logger->debug($e->getMessage(), ['exception' => $e]); |
| 461 | + return new JSONResponse(['error' => 'Invalid token'], Http::STATUS_FORBIDDEN); |
| 462 | + } catch (\Exception $e) { |
| 463 | + $this->logger->error($e->getMessage(), ['exception' => $e]); |
| 464 | + return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); |
| 465 | + } |
| 466 | + } |
| 467 | + |
| 468 | + #[NoAdminRequired] |
| 469 | + #[NoCSRFRequired] |
| 470 | + #[PublicPage] |
| 471 | + #[FrontpageRoute(verb: 'DELETE', url: 'wopi/settings')] |
| 472 | + public function deleteSettingsFile(string $fileId, string $access_token): JSONResponse { |
| 473 | + try { |
| 474 | + $wopi = $this->wopiMapper->getWopiForToken($access_token); |
| 475 | + if ($wopi->getTokenType() !== Wopi::TOKEN_TYPE_SETTING_AUTH) { |
| 476 | + return new JSONResponse(['error' => 'Invalid token type'], Http::STATUS_FORBIDDEN); |
| 477 | + } |
| 478 | + |
| 479 | + // Parse the dynamic file path from `fileId`, e.g. "/settings/systemconfig/wordbook/en_US (1).dic" |
| 480 | + $settingsUrl = new SettingsUrl($fileId); |
| 481 | + $type = $settingsUrl->getType(); |
| 482 | + $category = $settingsUrl->getCategory(); |
| 483 | + $fileName = $settingsUrl->getFileName(); |
| 484 | + $userId = $wopi->getEditorUid(); |
| 485 | + |
| 486 | + $this->settingsService->deleteSettingsFile($type, $category, $fileName, $userId); |
| 487 | + |
| 488 | + return new JSONResponse([ |
| 489 | + 'status' => 'success', |
| 490 | + 'message' => "File '$fileName' deleted from '$category' of type '$type'." |
| 491 | + ], Http::STATUS_OK); |
| 492 | + } catch (UnknownTokenException $e) { |
| 493 | + $this->logger->debug($e->getMessage(), ['exception' => $e]); |
| 494 | + return new JSONResponse(['error' => 'Invalid token'], Http::STATUS_FORBIDDEN); |
| 495 | + } catch (NotFoundException $e) { |
| 496 | + return new JSONResponse(['error' => 'File not found'], Http::STATUS_NOT_FOUND); |
| 497 | + } catch (NotPermittedException $e) { |
| 498 | + return new JSONResponse(['error' => 'Not permitted'], Http::STATUS_FORBIDDEN); |
| 499 | + } catch (\Exception $e) { |
| 500 | + $this->logger->error($e->getMessage(), ['exception' => $e]); |
| 501 | + return new JSONResponse(['error' => 'Internal Server Error'], Http::STATUS_INTERNAL_SERVER_ERROR); |
| 502 | + } |
| 503 | + } |
| 504 | + |
| 505 | + |
384 | 506 | /**
|
385 | 507 | * Given an access token and a fileId, replaces the files with the request body.
|
386 | 508 | * Expects a valid token in access_token parameter.
|
@@ -863,4 +985,17 @@ private function getWopiUrlForTemplate(Wopi $wopi): string {
|
863 | 985 | $nextcloudUrl = $this->appConfig->getNextcloudUrl() ?: trim($this->urlGenerator->getAbsoluteURL(''), '/');
|
864 | 986 | return $nextcloudUrl . '/index.php/apps/richdocuments/wopi/template/' . $wopi->getTemplateId() . '?access_token=' . $wopi->getToken();
|
865 | 987 | }
|
| 988 | + |
| 989 | + private function generateSettingToken(string $userId): string { |
| 990 | + return $this->settingsService->generateIframeToken('user', $userId)['token']; |
| 991 | + } |
| 992 | + |
| 993 | + private function generateSettings(string $userId, string $type): array { |
| 994 | + $nextcloudUrl = $this->appConfig->getNextcloudUrl() ?: trim($this->urlGenerator->getAbsoluteURL(''), '/'); |
| 995 | + $uri = $nextcloudUrl . '/index.php/apps/richdocuments/wopi/settings' . '?type=' . $type . '&access_token=' . $this->generateSettingToken($userId) . '&fileId=' . '-1'; |
| 996 | + return [ |
| 997 | + 'uri' => $uri, |
| 998 | + 'stamp' => time() |
| 999 | + ]; |
| 1000 | + } |
866 | 1001 | }
|
0 commit comments