aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/lib/Controller
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/lib/Controller')
-rw-r--r--apps/files/lib/Controller/ApiController.php363
-rw-r--r--apps/files/lib/Controller/ConversionApiController.php109
-rw-r--r--apps/files/lib/Controller/DirectEditingController.php129
-rw-r--r--apps/files/lib/Controller/DirectEditingViewController.php59
-rw-r--r--apps/files/lib/Controller/OpenLocalEditorController.php70
-rw-r--r--apps/files/lib/Controller/TemplateController.php115
-rw-r--r--apps/files/lib/Controller/TransferOwnershipController.php132
-rw-r--r--apps/files/lib/Controller/ViewController.php459
8 files changed, 746 insertions, 690 deletions
diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php
index 604cf9a3c64..8bb024fb698 100644
--- a/apps/files/lib/Controller/ApiController.php
+++ b/apps/files/lib/Controller/ApiController.php
@@ -1,134 +1,124 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Felix Nüsse <Felix.nuesse@t-online.de>
- * @author fnuesse <felix.nuesse@t-online.de>
- * @author fnuesse <fnuesse@techfak.uni-bielefeld.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Max Kovalenko <mxss1998@yandex.ru>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Nina Pypchenko <22447785+nina-py@users.noreply.github.com>
- * @author Richard Steinmetz <richard@steinmetz.cloud>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Tobias Kaminsky <tobias@kaminsky.me>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Files\Controller;
use OC\Files\Node\Node;
+use OCA\Files\Helper;
+use OCA\Files\ResponseDefinitions;
use OCA\Files\Service\TagService;
use OCA\Files\Service\UserConfig;
+use OCA\Files\Service\ViewConfig;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
+use OCP\AppFramework\Http\Attribute\StrictCookiesRequired;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\Http\StreamResponse;
use OCP\Files\File;
use OCP\Files\Folder;
+use OCP\Files\InvalidPathException;
+use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\Files\Storage\ISharedStorage;
+use OCP\Files\StorageNotAvailableException;
use OCP\IConfig;
+use OCP\IL10N;
use OCP\IPreview;
use OCP\IRequest;
+use OCP\IUser;
use OCP\IUserSession;
+use OCP\PreConditionNotMetException;
use OCP\Share\IManager;
use OCP\Share\IShare;
+use Psr\Log\LoggerInterface;
+use Throwable;
/**
- * Class ApiController
+ * @psalm-import-type FilesFolderTree from ResponseDefinitions
*
* @package OCA\Files\Controller
*/
class ApiController extends Controller {
- private TagService $tagService;
- private IManager $shareManager;
- private IPreview $previewManager;
- private IUserSession $userSession;
- private IConfig $config;
- private Folder $userFolder;
- private UserConfig $userConfig;
-
- /**
- * @param string $appName
- * @param IRequest $request
- * @param IUserSession $userSession
- * @param TagService $tagService
- * @param IPreview $previewManager
- * @param IManager $shareManager
- * @param IConfig $config
- * @param Folder $userFolder
- */
- public function __construct($appName,
- IRequest $request,
- IUserSession $userSession,
- TagService $tagService,
- IPreview $previewManager,
- IManager $shareManager,
- IConfig $config,
- Folder $userFolder,
- UserConfig $userConfig) {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private IUserSession $userSession,
+ private TagService $tagService,
+ private IPreview $previewManager,
+ private IManager $shareManager,
+ private IConfig $config,
+ private ?Folder $userFolder,
+ private UserConfig $userConfig,
+ private ViewConfig $viewConfig,
+ private IL10N $l10n,
+ private IRootFolder $rootFolder,
+ private LoggerInterface $logger,
+ ) {
parent::__construct($appName, $request);
- $this->userSession = $userSession;
- $this->tagService = $tagService;
- $this->previewManager = $previewManager;
- $this->shareManager = $shareManager;
- $this->config = $config;
- $this->userFolder = $userFolder;
- $this->userConfig = $userConfig;
}
/**
* Gets a thumbnail of the specified file
*
* @since API version 1.0
+ * @deprecated 32.0.0 Use the preview endpoint provided by core instead
*
- * @NoAdminRequired
- * @NoCSRFRequired
- * @StrictCookieRequired
- *
- * @param int $x
- * @param int $y
+ * @param int $x Width of the thumbnail
+ * @param int $y Height of the thumbnail
* @param string $file URL-encoded filename
- * @return DataResponse|FileDisplayResponse
+ * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array{message?: string}, array{}>
+ *
+ * 200: Thumbnail returned
+ * 400: Getting thumbnail is not possible
+ * 404: File not found
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
+ #[StrictCookiesRequired]
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function getThumbnail($x, $y, $file) {
if ($x < 1 || $y < 1) {
return new DataResponse(['message' => 'Requested size must be numeric and a positive value.'], Http::STATUS_BAD_REQUEST);
}
try {
- $file = $this->userFolder->get($file);
- if ($file instanceof Folder) {
+ $file = $this->userFolder?->get($file);
+ if ($file === null
+ || !($file instanceof File)
+ || ($file->getId() <= 0)
+ ) {
throw new NotFoundException();
}
- /** @var File $file */
+ // Validate the user is allowed to download the file (preview is some kind of download)
+ /** @var ISharedStorage $storage */
+ $storage = $file->getStorage();
+ if ($storage->instanceOfStorage(ISharedStorage::class)) {
+ /** @var IShare $share */
+ $share = $storage->getShare();
+ if (!$share->canSeeContent()) {
+ throw new NotFoundException();
+ }
+ }
+
$preview = $this->previewManager->getPreview($file, $x, $y, true);
return new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => $preview->getMimeType()]);
- } catch (NotFoundException $e) {
+ } catch (NotFoundException|NotPermittedException|InvalidPathException) {
return new DataResponse(['message' => 'File not found.'], Http::STATUS_NOT_FOUND);
} catch (\Exception $e) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
@@ -140,23 +130,22 @@ class ApiController extends Controller {
* The passed tags are absolute, which means they will
* replace the actual tag selection.
*
- * @NoAdminRequired
- *
* @param string $path path
* @param array|string $tags array of tags
* @return DataResponse
*/
+ #[NoAdminRequired]
public function updateFileTags($path, $tags = null) {
$result = [];
// if tags specified or empty array, update tags
if (!is_null($tags)) {
try {
$this->tagService->updateFileTags($path, $tags);
- } catch (\OCP\Files\NotFoundException $e) {
+ } catch (NotFoundException $e) {
return new DataResponse([
'message' => $e->getMessage()
], Http::STATUS_NOT_FOUND);
- } catch (\OCP\Files\StorageNotAvailableException $e) {
+ } catch (StorageNotAvailableException $e) {
return new DataResponse([
'message' => $e->getMessage()
], Http::STATUS_SERVICE_UNAVAILABLE);
@@ -178,7 +167,7 @@ class ApiController extends Controller {
$shareTypesForNodes = $this->getShareTypesForNodes($nodes);
return array_values(array_map(function (Node $node) use ($shareTypesForNodes) {
$shareTypes = $shareTypesForNodes[$node->getId()] ?? [];
- $file = \OCA\Files\Helper::formatFileInfo($node->getFileInfo());
+ $file = Helper::formatFileInfo($node->getFileInfo());
$file['hasPreview'] = $this->previewManager->isAvailable($node);
$parts = explode('/', dirname($node->getPath()), 4);
if (isset($parts[3])) {
@@ -209,6 +198,7 @@ class ApiController extends Controller {
IShare::TYPE_EMAIL,
IShare::TYPE_ROOM,
IShare::TYPE_DECK,
+ IShare::TYPE_SCIENCEMESH,
];
$shareTypes = [];
@@ -247,62 +237,143 @@ class ApiController extends Controller {
/**
* Returns a list of recently modified files.
*
- * @NoAdminRequired
- *
* @return DataResponse
*/
+ #[NoAdminRequired]
public function getRecentFiles() {
$nodes = $this->userFolder->getRecent(100);
$files = $this->formatNodes($nodes);
return new DataResponse(['files' => $files]);
}
+ /**
+ * @param \OCP\Files\Node[] $nodes
+ * @param int $depth The depth to traverse into the contents of each node
+ */
+ private function getChildren(array $nodes, int $depth = 1, int $currentDepth = 0): array {
+ if ($currentDepth >= $depth) {
+ return [];
+ }
+
+ $children = [];
+ foreach ($nodes as $node) {
+ if (!($node instanceof Folder)) {
+ continue;
+ }
+
+ $basename = basename($node->getPath());
+ $entry = [
+ 'id' => $node->getId(),
+ 'basename' => $basename,
+ 'children' => $this->getChildren($node->getDirectoryListing(), $depth, $currentDepth + 1),
+ ];
+ $displayName = $node->getName();
+ if ($basename !== $displayName) {
+ $entry['displayName'] = $displayName;
+ }
+ $children[] = $entry;
+ }
+ return $children;
+ }
/**
- * Returns the current logged-in user's storage stats.
+ * Returns the folder tree of the user
+ *
+ * @param string $path The path relative to the user folder
+ * @param int $depth The depth of the tree
*
- * @NoAdminRequired
+ * @return JSONResponse<Http::STATUS_OK, FilesFolderTree, array{}>|JSONResponse<Http::STATUS_UNAUTHORIZED|Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
+ *
+ * 200: Folder tree returned successfully
+ * 400: Invalid folder path
+ * 401: Unauthorized
+ * 404: Folder not found
+ */
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'GET', url: '/api/v1/folder-tree')]
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
+ public function getFolderTree(string $path = '/', int $depth = 1): JSONResponse {
+ $user = $this->userSession->getUser();
+ if (!($user instanceof IUser)) {
+ return new JSONResponse([
+ 'message' => $this->l10n->t('Failed to authorize'),
+ ], Http::STATUS_UNAUTHORIZED);
+ }
+ try {
+ $userFolder = $this->rootFolder->getUserFolder($user->getUID());
+ $userFolderPath = $userFolder->getPath();
+ $fullPath = implode('/', [$userFolderPath, trim($path, '/')]);
+ $node = $this->rootFolder->get($fullPath);
+ if (!($node instanceof Folder)) {
+ return new JSONResponse([
+ 'message' => $this->l10n->t('Invalid folder path'),
+ ], Http::STATUS_BAD_REQUEST);
+ }
+ $nodes = $node->getDirectoryListing();
+ $tree = $this->getChildren($nodes, $depth);
+ } catch (NotFoundException $e) {
+ return new JSONResponse([
+ 'message' => $this->l10n->t('Folder not found'),
+ ], Http::STATUS_NOT_FOUND);
+ } catch (Throwable $th) {
+ $this->logger->error($th->getMessage(), ['exception' => $th]);
+ $tree = [];
+ }
+ return new JSONResponse($tree);
+ }
+
+ /**
+ * Returns the current logged-in user's storage stats.
*
* @param ?string $dir the directory to get the storage stats from
* @return JSONResponse
*/
+ #[NoAdminRequired]
public function getStorageStats($dir = '/'): JSONResponse {
$storageInfo = \OC_Helper::getStorageInfo($dir ?: '/');
- return new JSONResponse(['message' => 'ok', 'data' => $storageInfo]);
+ $response = new JSONResponse(['message' => 'ok', 'data' => $storageInfo]);
+ $response->cacheFor(5 * 60);
+ return $response;
}
/**
- * Change the default sort mode
- *
- * @NoAdminRequired
+ * Set a user view config
*
- * @param string $mode
- * @param string $direction
- * @return Response
- * @throws \OCP\PreConditionNotMetException
+ * @param string $view
+ * @param string $key
+ * @param string|bool $value
+ * @return JSONResponse
*/
- public function updateFileSorting($mode, $direction) {
- $allowedMode = ['name', 'size', 'mtime'];
- $allowedDirection = ['asc', 'desc'];
- if (!in_array($mode, $allowedMode) || !in_array($direction, $allowedDirection)) {
- $response = new Response();
- $response->setStatus(Http::STATUS_UNPROCESSABLE_ENTITY);
- return $response;
+ #[NoAdminRequired]
+ public function setViewConfig(string $view, string $key, $value): JSONResponse {
+ try {
+ $this->viewConfig->setConfig($view, $key, (string)$value);
+ } catch (\InvalidArgumentException $e) {
+ return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
}
- $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'file_sorting', $mode);
- $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'file_sorting_direction', $direction);
- return new Response();
+
+ return new JSONResponse(['message' => 'ok', 'data' => $this->viewConfig->getConfig($view)]);
}
+
/**
- * Toggle default files user config
+ * Get the user view config
*
- * @NoAdminRequired
+ * @return JSONResponse
+ */
+ #[NoAdminRequired]
+ public function getViewConfigs(): JSONResponse {
+ return new JSONResponse(['message' => 'ok', 'data' => $this->viewConfig->getConfigs()]);
+ }
+
+ /**
+ * Set a user config
*
* @param string $key
* @param string|bool $value
* @return JSONResponse
*/
+ #[NoAdminRequired]
public function setConfig(string $key, $value): JSONResponse {
try {
$this->userConfig->setConfig($key, (string)$value);
@@ -317,10 +388,9 @@ class ApiController extends Controller {
/**
* Get the user config
*
- * @NoAdminRequired
- *
* @return JSONResponse
*/
+ #[NoAdminRequired]
public function getConfigs(): JSONResponse {
return new JSONResponse(['message' => 'ok', 'data' => $this->userConfig->getConfigs()]);
}
@@ -328,12 +398,11 @@ class ApiController extends Controller {
/**
* Toggle default for showing/hiding hidden files
*
- * @NoAdminRequired
- *
* @param bool $value
* @return Response
- * @throws \OCP\PreConditionNotMetException
+ * @throws PreConditionNotMetException
*/
+ #[NoAdminRequired]
public function showHiddenFiles(bool $value): Response {
$this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'show_hidden', $value ? '1' : '0');
return new Response();
@@ -342,12 +411,11 @@ class ApiController extends Controller {
/**
* Toggle default for cropping preview images
*
- * @NoAdminRequired
- *
* @param bool $value
* @return Response
- * @throws \OCP\PreConditionNotMetException
+ * @throws PreConditionNotMetException
*/
+ #[NoAdminRequired]
public function cropImagePreviews(bool $value): Response {
$this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'crop_image_previews', $value ? '1' : '0');
return new Response();
@@ -356,12 +424,11 @@ class ApiController extends Controller {
/**
* Toggle default for files grid view
*
- * @NoAdminRequired
- *
* @param bool $show
* @return Response
- * @throws \OCP\PreConditionNotMetException
+ * @throws PreConditionNotMetException
*/
+ #[NoAdminRequired]
public function showGridView(bool $show): Response {
$this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'show_grid', $show ? '1' : '0');
return new Response();
@@ -369,51 +436,27 @@ class ApiController extends Controller {
/**
* Get default settings for the grid view
- *
- * @NoAdminRequired
*/
+ #[NoAdminRequired]
public function getGridView() {
$status = $this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'show_grid', '0') === '1';
return new JSONResponse(['gridview' => $status]);
}
- /**
- * Toggle default for showing/hiding xxx folder
- *
- * @NoAdminRequired
- *
- * @param int $show
- * @param string $key the key of the folder
- *
- * @return Response
- * @throws \OCP\PreConditionNotMetException
- */
- public function toggleShowFolder(int $show, string $key): Response {
- if ($show !== 0 && $show !== 1) {
- return new DataResponse([
- 'message' => 'Invalid show value. Only 0 and 1 are allowed.'
- ], Http::STATUS_BAD_REQUEST);
- }
-
- $userId = $this->userSession->getUser()->getUID();
-
- // Set the new value and return it
- // Using a prefix prevents the user from setting arbitrary keys
- $this->config->setUserValue($userId, 'files', 'show_' . $key, (string)$show);
- return new JSONResponse([$key => $show]);
- }
-
- /**
- * Get sorting-order for custom sorting
- *
- * @NoAdminRequired
- *
- * @param string $folderpath
- * @return string
- * @throws \OCP\Files\NotFoundException
- */
- public function getNodeType($folderpath) {
- $node = $this->userFolder->get($folderpath);
- return $node->getType();
+ #[PublicPage]
+ #[NoCSRFRequired]
+ #[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
+ public function serviceWorker(): StreamResponse {
+ $response = new StreamResponse(__DIR__ . '/../../../../dist/preview-service-worker.js');
+ $response->setHeaders([
+ 'Content-Type' => 'application/javascript',
+ 'Service-Worker-Allowed' => '/'
+ ]);
+ $policy = new ContentSecurityPolicy();
+ $policy->addAllowedWorkerSrcDomain("'self'");
+ $policy->addAllowedScriptDomain("'self'");
+ $policy->addAllowedConnectDomain("'self'");
+ $response->setContentSecurityPolicy($policy);
+ return $response;
}
}
diff --git a/apps/files/lib/Controller/ConversionApiController.php b/apps/files/lib/Controller/ConversionApiController.php
new file mode 100644
index 00000000000..40a42d6ca4c
--- /dev/null
+++ b/apps/files/lib/Controller/ConversionApiController.php
@@ -0,0 +1,109 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Files\Controller;
+
+use OC\Files\Utils\PathHelper;
+use OC\ForbiddenException;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\UserRateLimit;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCS\OCSBadRequestException;
+use OCP\AppFramework\OCS\OCSException;
+use OCP\AppFramework\OCS\OCSForbiddenException;
+use OCP\AppFramework\OCS\OCSNotFoundException;
+use OCP\AppFramework\OCSController;
+use OCP\Files\Conversion\IConversionManager;
+use OCP\Files\File;
+use OCP\Files\GenericFileException;
+use OCP\Files\IRootFolder;
+use OCP\IL10N;
+use OCP\IRequest;
+use function OCP\Log\logger;
+
+class ConversionApiController extends OCSController {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private IConversionManager $fileConversionManager,
+ private IRootFolder $rootFolder,
+ private IL10N $l10n,
+ private ?string $userId,
+ ) {
+ parent::__construct($appName, $request);
+ }
+
+ /**
+ * Converts a file from one MIME type to another
+ *
+ * @param int $fileId ID of the file to be converted
+ * @param string $targetMimeType The MIME type to which you want to convert the file
+ * @param string|null $destination The target path of the converted file. Written to a temporary file if left empty
+ *
+ * @return DataResponse<Http::STATUS_CREATED, array{path: string, fileId: int}, array{}>
+ *
+ * 201: File was converted and written to the destination or temporary file
+ *
+ * @throws OCSException The file was unable to be converted
+ * @throws OCSNotFoundException The file to be converted was not found
+ */
+ #[NoAdminRequired]
+ #[UserRateLimit(limit: 25, period: 120)]
+ #[ApiRoute(verb: 'POST', url: '/api/v1/convert')]
+ public function convert(int $fileId, string $targetMimeType, ?string $destination = null): DataResponse {
+ $userFolder = $this->rootFolder->getUserFolder($this->userId);
+ $file = $userFolder->getFirstNodeById($fileId);
+
+ // Also throw a 404 if the file is not readable to not leak information
+ if (!($file instanceof File) || $file->isReadable() === false) {
+ throw new OCSNotFoundException($this->l10n->t('The file cannot be found'));
+ }
+
+ if ($destination !== null) {
+ $destination = PathHelper::normalizePath($destination);
+ $parentDir = dirname($destination);
+
+ if (!$userFolder->nodeExists($parentDir)) {
+ throw new OCSNotFoundException($this->l10n->t('The destination path does not exist: %1$s', [$parentDir]));
+ }
+
+ if (!$userFolder->get($parentDir)->isCreatable()) {
+ throw new OCSForbiddenException($this->l10n->t('You do not have permission to create a file at the specified location'));
+ }
+
+ $destination = $userFolder->getFullPath($destination);
+ }
+
+ try {
+ $convertedFile = $this->fileConversionManager->convert($file, $targetMimeType, $destination);
+ } catch (ForbiddenException $e) {
+ throw new OCSForbiddenException($e->getMessage());
+ } catch (GenericFileException $e) {
+ throw new OCSBadRequestException($e->getMessage());
+ } catch (\Exception $e) {
+ logger('files')->error($e->getMessage(), ['exception' => $e]);
+ throw new OCSException($this->l10n->t('The file could not be converted.'));
+ }
+
+ $convertedFileRelativePath = $userFolder->getRelativePath($convertedFile);
+ if ($convertedFileRelativePath === null) {
+ throw new OCSNotFoundException($this->l10n->t('Could not get relative path to converted file'));
+ }
+
+ $file = $userFolder->get($convertedFileRelativePath);
+ $fileId = $file->getId();
+
+ return new DataResponse([
+ 'path' => $convertedFileRelativePath,
+ 'fileId' => $fileId,
+ ], Http::STATUS_CREATED);
+ }
+}
diff --git a/apps/files/lib/Controller/DirectEditingController.php b/apps/files/lib/Controller/DirectEditingController.php
index c67150be8d4..c8addc33e98 100644
--- a/apps/files/lib/Controller/DirectEditingController.php
+++ b/apps/files/lib/Controller/DirectEditingController.php
@@ -1,70 +1,47 @@
<?php
+
/**
- * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
- *
- * @author Julius Härtl <jus@bitgrid.net>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files\Controller;
use Exception;
use OCA\Files\Service\DirectEditingService;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\DirectEditing\IManager;
use OCP\DirectEditing\RegisterDirectEditorEvent;
use OCP\EventDispatcher\IEventDispatcher;
-use OCP\ILogger;
use OCP\IRequest;
use OCP\IURLGenerator;
+use Psr\Log\LoggerInterface;
class DirectEditingController extends OCSController {
-
- /** @var IEventDispatcher */
- private $eventDispatcher;
-
- /** @var IManager */
- private $directEditingManager;
-
- /** @var IURLGenerator */
- private $urlGenerator;
-
- /** @var ILogger */
- private $logger;
-
- /** @var DirectEditingService */
- private $directEditingService;
-
- public function __construct($appName, IRequest $request, $corsMethods, $corsAllowedHeaders, $corsMaxAge,
- IEventDispatcher $eventDispatcher, IURLGenerator $urlGenerator, IManager $manager, DirectEditingService $directEditingService, ILogger $logger) {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ string $corsMethods,
+ string $corsAllowedHeaders,
+ int $corsMaxAge,
+ private IEventDispatcher $eventDispatcher,
+ private IURLGenerator $urlGenerator,
+ private IManager $directEditingManager,
+ private DirectEditingService $directEditingService,
+ private LoggerInterface $logger,
+ ) {
parent::__construct($appName, $request, $corsMethods, $corsAllowedHeaders, $corsMaxAge);
-
- $this->eventDispatcher = $eventDispatcher;
- $this->directEditingManager = $manager;
- $this->directEditingService = $directEditingService;
- $this->logger = $logger;
- $this->urlGenerator = $urlGenerator;
}
/**
- * @NoAdminRequired
+ * Get the direct editing capabilities
+ * @return DataResponse<Http::STATUS_OK, array{editors: array<string, array{id: string, name: string, mimetypes: list<string>, optionalMimetypes: list<string>, secure: bool}>, creators: array<string, array{id: string, editor: string, name: string, extension: string, templates: bool, mimetypes: list<string>}>}, array{}>
+ *
+ * 200: Direct editing capabilities returned
*/
+ #[NoAdminRequired]
public function info(): DataResponse {
$response = new DataResponse($this->directEditingService->getDirectEditingCapabilitites());
$response->setETag($this->directEditingService->getDirectEditingETag());
@@ -72,9 +49,20 @@ class DirectEditingController extends OCSController {
}
/**
- * @NoAdminRequired
+ * Create a file for direct editing
+ *
+ * @param string $path Path of the file
+ * @param string $editorId ID of the editor
+ * @param string $creatorId ID of the creator
+ * @param ?string $templateId ID of the template
+ *
+ * @return DataResponse<Http::STATUS_OK, array{url: string}, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ *
+ * 200: URL for direct editing returned
+ * 403: Opening file is not allowed
*/
- public function create(string $path, string $editorId, string $creatorId, string $templateId = null): DataResponse {
+ #[NoAdminRequired]
+ public function create(string $path, string $editorId, string $creatorId, ?string $templateId = null): DataResponse {
if (!$this->directEditingManager->isEnabled()) {
return new DataResponse(['message' => 'Direct editing is not enabled'], Http::STATUS_INTERNAL_SERVER_ERROR);
}
@@ -86,27 +74,47 @@ class DirectEditingController extends OCSController {
'url' => $this->urlGenerator->linkToRouteAbsolute('files.DirectEditingView.edit', ['token' => $token])
]);
} catch (Exception $e) {
- $this->logger->logException($e, ['message' => 'Exception when creating a new file through direct editing']);
+ $this->logger->error(
+ 'Exception when creating a new file through direct editing',
+ [
+ 'exception' => $e
+ ],
+ );
return new DataResponse(['message' => 'Failed to create file: ' . $e->getMessage()], Http::STATUS_FORBIDDEN);
}
}
/**
- * @NoAdminRequired
+ * Open a file for direct editing
+ *
+ * @param string $path Path of the file
+ * @param ?string $editorId ID of the editor
+ * @param ?int $fileId ID of the file
+ *
+ * @return DataResponse<Http::STATUS_OK, array{url: string}, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ *
+ * 200: URL for direct editing returned
+ * 403: Opening file is not allowed
*/
- public function open(string $path, string $editorId = null): DataResponse {
+ #[NoAdminRequired]
+ 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])
]);
} catch (Exception $e) {
- $this->logger->logException($e, ['message' => 'Exception when opening a file through direct editing']);
+ $this->logger->error(
+ 'Exception when opening a file through direct editing',
+ [
+ 'exception' => $e
+ ],
+ );
return new DataResponse(['message' => 'Failed to open file: ' . $e->getMessage()], Http::STATUS_FORBIDDEN);
}
}
@@ -114,8 +122,16 @@ class DirectEditingController extends OCSController {
/**
- * @NoAdminRequired
+ * Get the templates for direct editing
+ *
+ * @param string $editorId ID of the editor
+ * @param string $creatorId ID of the creator
+ *
+ * @return DataResponse<Http::STATUS_OK, array{templates: array<string, array{id: string, title: string, preview: ?string, extension: string, mimetype: string}>}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ *
+ * 200: Templates returned
*/
+ #[NoAdminRequired]
public function templates(string $editorId, string $creatorId): DataResponse {
if (!$this->directEditingManager->isEnabled()) {
return new DataResponse(['message' => 'Direct editing is not enabled'], Http::STATUS_INTERNAL_SERVER_ERROR);
@@ -125,7 +141,12 @@ class DirectEditingController extends OCSController {
try {
return new DataResponse($this->directEditingManager->getTemplates($editorId, $creatorId));
} catch (Exception $e) {
- $this->logger->logException($e);
+ $this->logger->error(
+ $e->getMessage(),
+ [
+ 'exception' => $e
+ ],
+ );
return new DataResponse(['message' => 'Failed to obtain template list: ' . $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
diff --git a/apps/files/lib/Controller/DirectEditingViewController.php b/apps/files/lib/Controller/DirectEditingViewController.php
index 06bde8d63d7..b13e68f7766 100644
--- a/apps/files/lib/Controller/DirectEditingViewController.php
+++ b/apps/files/lib/Controller/DirectEditingViewController.php
@@ -1,69 +1,50 @@
<?php
+
/**
- * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
- *
- * @author Julius Härtl <jus@bitgrid.net>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files\Controller;
use Exception;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
+use OCP\AppFramework\Http\Attribute\UseSession;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\Response;
use OCP\DirectEditing\IManager;
use OCP\DirectEditing\RegisterDirectEditorEvent;
use OCP\EventDispatcher\IEventDispatcher;
-use OCP\ILogger;
use OCP\IRequest;
+use Psr\Log\LoggerInterface;
+#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class DirectEditingViewController extends Controller {
-
- /** @var IEventDispatcher */
- private $eventDispatcher;
-
- /** @var IManager */
- private $directEditingManager;
-
- /** @var ILogger */
- private $logger;
-
- public function __construct($appName, IRequest $request, IEventDispatcher $eventDispatcher, IManager $manager, ILogger $logger) {
+ public function __construct(
+ $appName,
+ IRequest $request,
+ private IEventDispatcher $eventDispatcher,
+ private IManager $directEditingManager,
+ private LoggerInterface $logger,
+ ) {
parent::__construct($appName, $request);
-
- $this->eventDispatcher = $eventDispatcher;
- $this->directEditingManager = $manager;
- $this->logger = $logger;
}
/**
- * @PublicPage
- * @NoCSRFRequired
- *
* @param string $token
* @return Response
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
+ #[UseSession]
public function edit(string $token): Response {
$this->eventDispatcher->dispatchTyped(new RegisterDirectEditorEvent($this->directEditingManager));
try {
return $this->directEditingManager->edit($token);
} catch (Exception $e) {
- $this->logger->logException($e);
+ $this->logger->error($e->getMessage(), ['exception' => $e]);
return new NotFoundResponse();
}
}
diff --git a/apps/files/lib/Controller/OpenLocalEditorController.php b/apps/files/lib/Controller/OpenLocalEditorController.php
index 7d784196361..b000304eef6 100644
--- a/apps/files/lib/Controller/OpenLocalEditorController.php
+++ b/apps/files/lib/Controller/OpenLocalEditorController.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
- *
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files\Controller;
@@ -30,6 +13,9 @@ use OCA\Files\Db\OpenLocalEditor;
use OCA\Files\Db\OpenLocalEditorMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\BruteForceProtection;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\UserRateLimit;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\Utility\ITimeFactory;
@@ -43,34 +29,29 @@ class OpenLocalEditorController extends OCSController {
public const TOKEN_DURATION = 600; // 10 Minutes
public const TOKEN_RETRIES = 50;
- protected ITimeFactory $timeFactory;
- protected OpenLocalEditorMapper $mapper;
- protected ISecureRandom $secureRandom;
- protected LoggerInterface $logger;
- protected ?string $userId;
-
public function __construct(
string $appName,
IRequest $request,
- ITimeFactory $timeFactory,
- OpenLocalEditorMapper $mapper,
- ISecureRandom $secureRandom,
- LoggerInterface $logger,
- ?string $userId
+ protected ITimeFactory $timeFactory,
+ protected OpenLocalEditorMapper $mapper,
+ protected ISecureRandom $secureRandom,
+ protected LoggerInterface $logger,
+ protected ?string $userId,
) {
parent::__construct($appName, $request);
-
- $this->timeFactory = $timeFactory;
- $this->mapper = $mapper;
- $this->secureRandom = $secureRandom;
- $this->logger = $logger;
- $this->userId = $userId;
}
/**
- * @NoAdminRequired
- * @UserRateThrottle(limit=10, period=120)
+ * Create a local editor
+ *
+ * @param string $path Path of the file
+ *
+ * @return DataResponse<Http::STATUS_OK, array{userId: ?string, pathHash: string, expirationTime: int, token: string}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, list<empty>, array{}>
+ *
+ * 200: Local editor returned
*/
+ #[NoAdminRequired]
+ #[UserRateLimit(limit: 10, period: 120)]
public function create(string $path): DataResponse {
$pathHash = sha1($path);
@@ -105,9 +86,18 @@ class OpenLocalEditorController extends OCSController {
}
/**
- * @NoAdminRequired
- * @BruteForceProtection(action=openLocalEditor)
+ * Validate a local editor
+ *
+ * @param string $path Path of the file
+ * @param string $token Token of the local editor
+ *
+ * @return DataResponse<Http::STATUS_OK, array{userId: string, pathHash: string, expirationTime: int, token: string}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, list<empty>, array{}>
+ *
+ * 200: Local editor validated successfully
+ * 404: Local editor not found
*/
+ #[NoAdminRequired]
+ #[BruteForceProtection(action: 'openLocalEditor')]
public function validate(string $path, string $token): DataResponse {
$pathHash = sha1($path);
diff --git a/apps/files/lib/Controller/TemplateController.php b/apps/files/lib/Controller/TemplateController.php
index d04d86760e6..ee4c86941c7 100644
--- a/apps/files/lib/Controller/TemplateController.php
+++ b/apps/files/lib/Controller/TemplateController.php
@@ -3,72 +3,123 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
- *
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Julius Härtl <jus@bitgrid.net>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files\Controller;
+use OCA\Files\ResponseDefinitions;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCSController;
use OCP\Files\GenericFileException;
use OCP\Files\Template\ITemplateManager;
+use OCP\Files\Template\Template;
+use OCP\Files\Template\TemplateFileCreator;
use OCP\IRequest;
+/**
+ * @psalm-import-type FilesTemplateFile from ResponseDefinitions
+ * @psalm-import-type FilesTemplateFileCreator from ResponseDefinitions
+ * @psalm-import-type FilesTemplateFileCreatorWithTemplates from ResponseDefinitions
+ * @psalm-import-type FilesTemplateField from ResponseDefinitions
+ * @psalm-import-type FilesTemplate from ResponseDefinitions
+ */
class TemplateController extends OCSController {
- protected $templateManager;
-
- public function __construct($appName, IRequest $request, ITemplateManager $templateManager) {
+ public function __construct(
+ $appName,
+ IRequest $request,
+ protected ITemplateManager $templateManager,
+ ) {
parent::__construct($appName, $request);
- $this->templateManager = $templateManager;
}
/**
- * @NoAdminRequired
+ * List the available templates
+ *
+ * @return DataResponse<Http::STATUS_OK, list<FilesTemplateFileCreatorWithTemplates>, array{}>
+ *
+ * 200: Available templates returned
*/
+ #[NoAdminRequired]
public function list(): DataResponse {
- return new DataResponse($this->templateManager->listTemplates());
+ /* Convert embedded Template instances to arrays to match return type */
+ return new DataResponse(array_map(static function (array $templateFileCreator) {
+ $templateFileCreator['templates'] = array_map(static fn (Template $template) => $template->jsonSerialize(), $templateFileCreator['templates']);
+ return $templateFileCreator;
+ }, $this->templateManager->listTemplates()));
+ }
+
+ /**
+ * List the fields for the template specified by the given file ID
+ *
+ * @param int $fileId File ID of the template
+ * @return DataResponse<Http::STATUS_OK, array<string, FilesTemplateField>, array{}>
+ *
+ * 200: Fields returned
+ */
+ #[NoAdminRequired]
+ public function listTemplateFields(int $fileId): DataResponse {
+ $fields = $this->templateManager->listTemplateFields($fileId);
+
+ return new DataResponse(
+ array_merge([], ...$fields),
+ Http::STATUS_OK
+ );
}
/**
- * @NoAdminRequired
- * @throws OCSForbiddenException
+ * Create a template
+ *
+ * @param string $filePath Path of the file
+ * @param string $templatePath Name of the template
+ * @param string $templateType Type of the template
+ * @param list<FilesTemplateField> $templateFields Fields of the template
+ *
+ * @return DataResponse<Http::STATUS_OK, FilesTemplateFile, array{}>
+ * @throws OCSForbiddenException Creating template is not allowed
+ *
+ * 200: Template created successfully
*/
- public function create(string $filePath, string $templatePath = '', string $templateType = 'user'): DataResponse {
+ #[NoAdminRequired]
+ public function create(
+ string $filePath,
+ string $templatePath = '',
+ string $templateType = 'user',
+ array $templateFields = [],
+ ): DataResponse {
try {
- return new DataResponse($this->templateManager->createFromTemplate($filePath, $templatePath, $templateType));
+ return new DataResponse($this->templateManager->createFromTemplate(
+ $filePath,
+ $templatePath,
+ $templateType,
+ $templateFields));
} catch (GenericFileException $e) {
throw new OCSForbiddenException($e->getMessage());
}
}
/**
- * @NoAdminRequired
+ * Initialize the template directory
+ *
+ * @param string $templatePath Path of the template directory
+ * @param bool $copySystemTemplates Whether to copy the system templates to the template directory
+ *
+ * @return DataResponse<Http::STATUS_OK, array{template_path: string, templates: list<FilesTemplateFileCreator>}, array{}>
+ * @throws OCSForbiddenException Initializing the template directory is not allowed
+ *
+ * 200: Template directory initialized successfully
*/
+ #[NoAdminRequired]
public function path(string $templatePath = '', bool $copySystemTemplates = false) {
try {
+ /** @var string $templatePath */
$templatePath = $this->templateManager->initializeTemplateDirectory($templatePath, null, $copySystemTemplates);
return new DataResponse([
'template_path' => $templatePath,
- 'templates' => $this->templateManager->listCreators()
+ 'templates' => array_values(array_map(fn (TemplateFileCreator $creator) => $creator->jsonSerialize(), $this->templateManager->listCreators())),
]);
} catch (\Exception $e) {
throw new OCSForbiddenException($e->getMessage());
diff --git a/apps/files/lib/Controller/TransferOwnershipController.php b/apps/files/lib/Controller/TransferOwnershipController.php
index 5abd65444bf..51a25400efb 100644
--- a/apps/files/lib/Controller/TransferOwnershipController.php
+++ b/apps/files/lib/Controller/TransferOwnershipController.php
@@ -3,40 +3,22 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files\Controller;
use OCA\Files\BackgroundJob\TransferOwnership;
use OCA\Files\Db\TransferOwnership as TransferOwnershipEntity;
use OCA\Files\Db\TransferOwnershipMapper;
-use OCP\Files\IHomeStorage;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
+use OCP\Files\IHomeStorage;
use OCP\Files\IRootFolder;
use OCP\IRequest;
use OCP\IUserManager;
@@ -44,45 +26,34 @@ use OCP\Notification\IManager as NotificationManager;
class TransferOwnershipController extends OCSController {
- /** @var string */
- private $userId;
- /** @var NotificationManager */
- private $notificationManager;
- /** @var ITimeFactory */
- private $timeFactory;
- /** @var IJobList */
- private $jobList;
- /** @var TransferOwnershipMapper */
- private $mapper;
- /** @var IUserManager */
- private $userManager;
- /** @var IRootFolder */
- private $rootFolder;
-
- public function __construct(string $appName,
- IRequest $request,
- string $userId,
- NotificationManager $notificationManager,
- ITimeFactory $timeFactory,
- IJobList $jobList,
- TransferOwnershipMapper $mapper,
- IUserManager $userManager,
- IRootFolder $rootFolder) {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private string $userId,
+ private NotificationManager $notificationManager,
+ private ITimeFactory $timeFactory,
+ private IJobList $jobList,
+ private TransferOwnershipMapper $mapper,
+ private IUserManager $userManager,
+ private IRootFolder $rootFolder,
+ ) {
parent::__construct($appName, $request);
-
- $this->userId = $userId;
- $this->notificationManager = $notificationManager;
- $this->timeFactory = $timeFactory;
- $this->jobList = $jobList;
- $this->mapper = $mapper;
- $this->userManager = $userManager;
- $this->rootFolder = $rootFolder;
}
/**
- * @NoAdminRequired
+ * Transfer the ownership to another user
+ *
+ * @param string $recipient Username of the recipient
+ * @param string $path Path of the file
+ *
+ * @return DataResponse<Http::STATUS_OK|Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN, list<empty>, array{}>
+ *
+ * 200: Ownership transferred successfully
+ * 400: Transferring ownership is not possible
+ * 403: Transferring ownership is not allowed
*/
+ #[NoAdminRequired]
public function transfer(string $recipient, string $path): DataResponse {
$recipientUser = $this->userManager->get($recipient);
@@ -126,8 +97,17 @@ class TransferOwnershipController extends OCSController {
}
/**
- * @NoAdminRequired
+ * Accept an ownership transfer
+ *
+ * @param int $id ID of the ownership transfer
+ *
+ * @return DataResponse<Http::STATUS_OK|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, list<empty>, array{}>
+ *
+ * 200: Ownership transfer accepted successfully
+ * 403: Accepting ownership transfer is not allowed
+ * 404: Ownership transfer not found
*/
+ #[NoAdminRequired]
public function accept(int $id): DataResponse {
try {
$transferOwnership = $this->mapper->getById($id);
@@ -139,28 +119,30 @@ class TransferOwnershipController extends OCSController {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
+ $this->jobList->add(TransferOwnership::class, [
+ 'id' => $transferOwnership->getId(),
+ ]);
+
$notification = $this->notificationManager->createNotification();
$notification->setApp('files')
->setObject('transfer', (string)$id);
$this->notificationManager->markProcessed($notification);
- $newTransferOwnership = new TransferOwnershipEntity();
- $newTransferOwnership->setNodeName($transferOwnership->getNodeName());
- $newTransferOwnership->setFileId($transferOwnership->getFileId());
- $newTransferOwnership->setSourceUser($transferOwnership->getSourceUser());
- $newTransferOwnership->setTargetUser($transferOwnership->getTargetUser());
- $this->mapper->insert($newTransferOwnership);
-
- $this->jobList->add(TransferOwnership::class, [
- 'id' => $newTransferOwnership->getId(),
- ]);
-
return new DataResponse([], Http::STATUS_OK);
}
/**
- * @NoAdminRequired
+ * Reject an ownership transfer
+ *
+ * @param int $id ID of the ownership transfer
+ *
+ * @return DataResponse<Http::STATUS_OK|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, list<empty>, array{}>
+ *
+ * 200: Ownership transfer rejected successfully
+ * 403: Rejecting ownership transfer is not allowed
+ * 404: Ownership transfer not found
*/
+ #[NoAdminRequired]
public function reject(int $id): DataResponse {
try {
$transferOwnership = $this->mapper->getById($id);
@@ -177,20 +159,10 @@ class TransferOwnershipController extends OCSController {
->setObject('transfer', (string)$id);
$this->notificationManager->markProcessed($notification);
- $notification = $this->notificationManager->createNotification();
- $notification->setUser($transferOwnership->getSourceUser())
- ->setApp($this->appName)
- ->setDateTime($this->timeFactory->getDateTime())
- ->setSubject('transferownershipRequestDenied', [
- 'sourceUser' => $transferOwnership->getSourceUser(),
- 'targetUser' => $transferOwnership->getTargetUser(),
- 'nodeName' => $transferOwnership->getNodeName()
- ])
- ->setObject('transfer', (string)$transferOwnership->getId());
- $this->notificationManager->notify($notification);
-
$this->mapper->delete($transferOwnership);
+ // A "request denied" notification will be created by Notifier::dismissNotification
+
return new DataResponse([], Http::STATUS_OK);
}
}
diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php
index b607764e602..ecf21cef313 100644
--- a/apps/files/lib/Controller/ViewController.php
+++ b/apps/files/lib/Controller/ViewController.php
@@ -1,53 +1,32 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author fnuesse <felix.nuesse@t-online.de>
- * @author fnuesse <fnuesse@techfak.uni-bielefeld.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Max Kovalenko <mxss1998@yandex.ru>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Nina Pypchenko <22447785+nina-py@users.noreply.github.com>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Files\Controller;
-use OCA\Files\Activity\Helper;
+use OC\Files\FilenameValidator;
+use OC\Files\Filesystem;
use OCA\Files\AppInfo\Application;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
+use OCA\Files\Event\LoadSearchPlugins;
use OCA\Files\Event\LoadSidebar;
use OCA\Files\Service\UserConfig;
+use OCA\Files\Service\ViewConfig;
use OCA\Viewer\Event\LoadViewer;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
+use OCP\Authentication\TwoFactorAuth\IRegistry;
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as ResourcesLoadAdditionalScriptsEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Folder;
@@ -59,359 +38,269 @@ use OCP\IL10N;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
-use OCP\Share\IManager;
+use OCP\Util;
/**
- * Class ViewController
- *
* @package OCA\Files\Controller
*/
+#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class ViewController extends Controller {
- private IURLGenerator $urlGenerator;
- private IL10N $l10n;
- private IConfig $config;
- private IEventDispatcher $eventDispatcher;
- private IUserSession $userSession;
- private IAppManager $appManager;
- private IRootFolder $rootFolder;
- private Helper $activityHelper;
- private IInitialState $initialState;
- private ITemplateManager $templateManager;
- private IManager $shareManager;
- private UserConfig $userConfig;
-
- public function __construct(string $appName,
+
+ public function __construct(
+ string $appName,
IRequest $request,
- IURLGenerator $urlGenerator,
- IL10N $l10n,
- IConfig $config,
- IEventDispatcher $eventDispatcher,
- IUserSession $userSession,
- IAppManager $appManager,
- IRootFolder $rootFolder,
- Helper $activityHelper,
- IInitialState $initialState,
- ITemplateManager $templateManager,
- IManager $shareManager,
- UserConfig $userConfig
+ private IURLGenerator $urlGenerator,
+ private IL10N $l10n,
+ private IConfig $config,
+ private IEventDispatcher $eventDispatcher,
+ private IUserSession $userSession,
+ private IAppManager $appManager,
+ private IRootFolder $rootFolder,
+ private IInitialState $initialState,
+ private ITemplateManager $templateManager,
+ private UserConfig $userConfig,
+ private ViewConfig $viewConfig,
+ private FilenameValidator $filenameValidator,
+ private IRegistry $twoFactorRegistry,
) {
parent::__construct($appName, $request);
- $this->urlGenerator = $urlGenerator;
- $this->l10n = $l10n;
- $this->config = $config;
- $this->eventDispatcher = $eventDispatcher;
- $this->userSession = $userSession;
- $this->appManager = $appManager;
- $this->rootFolder = $rootFolder;
- $this->activityHelper = $activityHelper;
- $this->initialState = $initialState;
- $this->templateManager = $templateManager;
- $this->shareManager = $shareManager;
- $this->userConfig = $userConfig;
- }
-
- /**
- * @param string $appName
- * @param string $scriptName
- * @return string
- */
- protected function renderScript($appName, $scriptName) {
- $content = '';
- $appPath = \OC_App::getAppPath($appName);
- $scriptPath = $appPath . '/' . $scriptName;
- if (file_exists($scriptPath)) {
- // TODO: sanitize path / script name ?
- ob_start();
- include $scriptPath;
- $content = ob_get_contents();
- @ob_end_clean();
- }
-
- return $content;
}
/**
* FIXME: Replace with non static code
*
* @return array
- * @throws \OCP\Files\NotFoundException
+ * @throws NotFoundException
*/
protected function getStorageInfo(string $dir = '/') {
- \OC_Util::setupFS();
- $rootInfo = \OC\Files\Filesystem::getFileInfo('/', false);
+ $rootInfo = Filesystem::getFileInfo('/', false);
return \OC_Helper::getStorageInfo($dir, $rootInfo ?: null);
}
/**
- * @NoCSRFRequired
- * @NoAdminRequired
- *
* @param string $fileid
* @return TemplateResponse|RedirectResponse
- * @throws NotFoundException
*/
- public function showFile(string $fileid = null, int $openfile = 1): Response {
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
+ public function showFile(?string $fileid = null, ?string $opendetails = null, ?string $openfile = null): Response {
+ if (!$fileid) {
+ return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.index'));
+ }
+
// This is the entry point from the `/f/{fileid}` URL which is hardcoded in the server.
try {
- return $this->redirectToFile($fileid, $openfile !== 0);
+ return $this->redirectToFile((int)$fileid, $opendetails, $openfile);
} catch (NotFoundException $e) {
- return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.index', ['fileNotFound' => true]));
+ // Keep the fileid even if not found, it will be used
+ // to detect the file could not be found and warn the user
+ return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.indexViewFileid', ['fileid' => $fileid, 'view' => 'files']));
}
}
+
+ /**
+ * @param string $dir
+ * @param string $view
+ * @param string $fileid
+ * @return TemplateResponse|RedirectResponse
+ */
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
+ public function indexView($dir = '', $view = '', $fileid = null) {
+ return $this->index($dir, $view, $fileid);
+ }
+
/**
- * @NoCSRFRequired
- * @NoAdminRequired
- * @UseSession
- *
* @param string $dir
* @param string $view
* @param string $fileid
- * @param bool $fileNotFound
- * @param string $openfile - the openfile URL parameter if it was present in the initial request
* @return TemplateResponse|RedirectResponse
- * @throws NotFoundException
*/
- public function index($dir = '', $view = '', $fileid = null, $fileNotFound = false, $openfile = null) {
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
+ public function indexViewFileid($dir = '', $view = '', $fileid = null) {
+ return $this->index($dir, $view, $fileid);
+ }
- if ($fileid !== null && $dir === '') {
+ /**
+ * @param string $dir
+ * @param string $view
+ * @param string $fileid
+ * @return TemplateResponse|RedirectResponse
+ */
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
+ public function index($dir = '', $view = '', $fileid = null) {
+ if ($fileid !== null && $view !== 'trashbin') {
try {
- return $this->redirectToFile($fileid);
+ return $this->redirectToFileIfInTrashbin((int)$fileid);
} catch (NotFoundException $e) {
- return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.index', ['fileNotFound' => true]));
}
}
- $nav = new \OCP\Template('files', 'appnavigation', '');
-
// Load the files we need
- \OCP\Util::addStyle('files', 'merged');
- \OCP\Util::addScript('files', 'merged-index', 'files');
- \OCP\Util::addScript('files', 'main');
+ Util::addInitScript('files', 'init');
+ Util::addScript('files', 'main');
- // mostly for the home storage's free space
- // FIXME: Make non static
- $storageInfo = $this->getStorageInfo();
-
- $userId = $this->userSession->getUser()->getUID();
-
- // Get all the user favorites to create a submenu
- try {
- $favElements = $this->activityHelper->getFavoriteFilePaths($userId);
- } catch (\RuntimeException $e) {
- $favElements['folders'] = [];
- }
-
- $collapseClasses = '';
- if (count($favElements['folders']) > 0) {
- $collapseClasses = 'collapsible';
- }
-
- $favoritesSublistArray = [];
-
- $navBarPositionPosition = 6;
- foreach ($favElements['folders'] as $favElement) {
- $element = [
- 'id' => str_replace('/', '-', $favElement),
- 'dir' => $favElement,
- 'order' => $navBarPositionPosition,
- 'name' => basename($favElement),
- 'icon' => 'folder',
- 'params' => [
- 'view' => 'files',
- 'dir' => $favElement,
- ],
- ];
-
- array_push($favoritesSublistArray, $element);
- $navBarPositionPosition++;
- }
-
- $navItems = \OCA\Files\App::getNavigationManager()->getAll();
-
- // add the favorites entry in menu
- $navItems['favorites']['sublist'] = $favoritesSublistArray;
- $navItems['favorites']['classes'] = $collapseClasses;
-
- // parse every menu and add the expanded user value
- foreach ($navItems as $key => $item) {
- $navItems[$key]['expanded'] = $this->config->getUserValue($userId, 'files', 'show_' . $item['id'], '0') === '1';
+ $user = $this->userSession->getUser();
+ $userId = $user->getUID();
+
+ // If the file doesn't exists in the folder and
+ // exists in only one occurrence, redirect to that file
+ // in the correct folder
+ if ($fileid && $dir !== '') {
+ $baseFolder = $this->rootFolder->getUserFolder($userId);
+ $nodes = $baseFolder->getById((int)$fileid);
+ if (!empty($nodes)) {
+ $nodePath = $baseFolder->getRelativePath($nodes[0]->getPath());
+ $relativePath = $nodePath ? dirname($nodePath) : '';
+ // If the requested path does not contain the file id
+ // or if the requested path is not the file id itself
+ if (count($nodes) === 1 && $relativePath !== $dir && $nodePath !== $dir) {
+ return $this->redirectToFile((int)$fileid);
+ }
+ }
}
- $nav->assign('navigationItems', $navItems);
-
- $contentItems = [];
-
try {
// If view is files, we use the directory, otherwise we use the root storage
- $storageInfo = $this->getStorageInfo(($view === 'files' && $dir) ? $dir : '/');
- } catch(\Exception $e) {
+ $storageInfo = $this->getStorageInfo(($view === 'files' && $dir) ? $dir : '/');
+ } catch (\Exception $e) {
$storageInfo = $this->getStorageInfo();
}
$this->initialState->provideInitialState('storageStats', $storageInfo);
- $this->initialState->provideInitialState('navigation', $navItems);
$this->initialState->provideInitialState('config', $this->userConfig->getConfigs());
+ $this->initialState->provideInitialState('viewConfigs', $this->viewConfig->getConfigs());
- // render the container content for every navigation item
- foreach ($navItems as $item) {
- $content = '';
- if (isset($item['script'])) {
- $content = $this->renderScript($item['appname'], $item['script']);
- }
- // parse submenus
- if (isset($item['sublist'])) {
- foreach ($item['sublist'] as $subitem) {
- $subcontent = '';
- if (isset($subitem['script'])) {
- $subcontent = $this->renderScript($subitem['appname'], $subitem['script']);
- }
- $contentItems[$subitem['id']] = [
- 'id' => $subitem['id'],
- 'content' => $subcontent
- ];
- }
- }
- $contentItems[$item['id']] = [
- 'id' => $item['id'],
- 'content' => $content
- ];
- }
+ // File sorting user config
+ $filesSortingConfig = json_decode($this->config->getUserValue($userId, 'files', 'files_sorting_configs', '{}'), true);
+ $this->initialState->provideInitialState('filesSortingConfig', $filesSortingConfig);
+
+ // Forbidden file characters (deprecated use capabilities)
+ // TODO: Remove with next release of `@nextcloud/files`
+ $forbiddenCharacters = $this->filenameValidator->getForbiddenCharacters();
+ $this->initialState->provideInitialState('forbiddenCharacters', $forbiddenCharacters);
- $this->eventDispatcher->dispatchTyped(new ResourcesLoadAdditionalScriptsEvent());
$event = new LoadAdditionalScriptsEvent();
$this->eventDispatcher->dispatchTyped($event);
+ $this->eventDispatcher->dispatchTyped(new ResourcesLoadAdditionalScriptsEvent());
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
+ $this->eventDispatcher->dispatchTyped(new LoadSearchPlugins());
// Load Viewer scripts
if (class_exists(LoadViewer::class)) {
$this->eventDispatcher->dispatchTyped(new LoadViewer());
}
+ $this->initialState->provideInitialState('templates_enabled', ($this->config->getSystemValueString('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton') !== '') || ($this->config->getSystemValueString('templatedirectory', \OC::$SERVERROOT . '/core/skeleton/Templates') !== ''));
$this->initialState->provideInitialState('templates_path', $this->templateManager->hasTemplateDirectory() ? $this->templateManager->getTemplatePath() : false);
$this->initialState->provideInitialState('templates', $this->templateManager->listCreators());
- $params = [];
- $params['usedSpacePercent'] = (int) $storageInfo['relative'];
- $params['owner'] = $storageInfo['owner'] ?? '';
- $params['ownerDisplayName'] = $storageInfo['ownerDisplayName'] ?? '';
- $params['isPublic'] = false;
- $params['allowShareWithLink'] = $this->shareManager->shareApiAllowLinks() ? 'yes' : 'no';
- $params['defaultFileSorting'] = $this->config->getUserValue($userId, 'files', 'file_sorting', 'name');
- $params['defaultFileSortingDirection'] = $this->config->getUserValue($userId, 'files', 'file_sorting_direction', 'asc');
- $params['showgridview'] = $this->config->getUserValue($userId, 'files', 'show_grid', false);
- $showHidden = (bool) $this->config->getUserValue($userId, 'files', 'show_hidden', false);
- $params['showHiddenFiles'] = $showHidden ? 1 : 0;
- $cropImagePreviews = (bool) $this->config->getUserValue($userId, 'files', 'crop_image_previews', true);
- $params['cropImagePreviews'] = $cropImagePreviews ? 1 : 0;
- $params['fileNotFound'] = $fileNotFound ? 1 : 0;
- $params['appNavigation'] = $nav;
- $params['appContents'] = $contentItems;
- $params['hiddenFields'] = $event->getHiddenFields();
+ $isTwoFactorEnabled = false;
+ foreach ($this->twoFactorRegistry->getProviderStates($user) as $providerId => $providerState) {
+ if ($providerId !== 'backup_codes' && $providerState === true) {
+ $isTwoFactorEnabled = true;
+ }
+ }
+
+ $this->initialState->provideInitialState('isTwoFactorEnabled', $isTwoFactorEnabled);
$response = new TemplateResponse(
Application::APP_ID,
'index',
- $params
);
$policy = new ContentSecurityPolicy();
$policy->addAllowedFrameDomain('\'self\'');
+ // Allow preview service worker
+ $policy->addAllowedWorkerSrcDomain('\'self\'');
$response->setContentSecurityPolicy($policy);
- $this->provideInitialState($dir, $openfile);
-
return $response;
}
/**
- * Add openFileInfo in initialState if $openfile is set.
- * @param string $dir - the ?dir= URL param
- * @param string $openfile - the ?openfile= URL param
- * @return void
+ * Redirects to the trashbin file list and highlight the given file id
+ *
+ * @param int $fileId file id to show
+ * @return RedirectResponse redirect response or not found response
+ * @throws NotFoundException
*/
- private function provideInitialState(string $dir, ?string $openfile): void {
- if ($openfile === null) {
- return;
- }
-
- $user = $this->userSession->getUser();
-
- if ($user === null) {
- return;
- }
-
- $uid = $user->getUID();
- $userFolder = $this->rootFolder->getUserFolder($uid);
- $nodes = $userFolder->getById((int) $openfile);
- $node = array_shift($nodes);
-
- if ($node === null) {
- return;
- }
+ private function redirectToFileIfInTrashbin($fileId): RedirectResponse {
+ $uid = $this->userSession->getUser()->getUID();
+ $baseFolder = $this->rootFolder->getUserFolder($uid);
+ $node = $baseFolder->getFirstNodeById($fileId);
+ $params = [];
- // properly format full path and make sure
- // we're relative to the user home folder
- $isRoot = $node === $userFolder;
- $path = $userFolder->getRelativePath($node->getPath());
- $directory = $userFolder->getRelativePath($node->getParent()->getPath());
+ if (!$node && $this->appManager->isEnabledForUser('files_trashbin')) {
+ /** @var Folder */
+ $baseFolder = $this->rootFolder->get($uid . '/files_trashbin/files/');
+ $node = $baseFolder->getFirstNodeById($fileId);
+ $params['view'] = 'trashbin';
- // Prevent opening a file from another folder.
- if ($dir !== $directory) {
- return;
+ if ($node) {
+ $params['fileid'] = $fileId;
+ if ($node instanceof Folder) {
+ // set the full path to enter the folder
+ $params['dir'] = $baseFolder->getRelativePath($node->getPath());
+ } else {
+ // set parent path as dir
+ $params['dir'] = $baseFolder->getRelativePath($node->getParent()->getPath());
+ }
+ return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.indexViewFileid', $params));
+ }
}
-
- $this->initialState->provideInitialState(
- 'openFileInfo', [
- 'id' => $node->getId(),
- 'name' => $isRoot ? '' : $node->getName(),
- 'path' => $path,
- 'directory' => $directory,
- 'mime' => $node->getMimetype(),
- 'type' => $node->getType(),
- 'permissions' => $node->getPermissions(),
- ]
- );
+ throw new NotFoundException();
}
/**
* Redirects to the file list and highlight the given file id
*
- * @param string $fileId file id to show
- * @param bool $setOpenfile - whether or not to set the openfile URL parameter
+ * @param int $fileId file id to show
+ * @param string|null $openDetails open details parameter
+ * @param string|null $openFile open file parameter
* @return RedirectResponse redirect response or not found response
- * @throws \OCP\Files\NotFoundException
+ * @throws NotFoundException
*/
- private function redirectToFile($fileId, bool $setOpenfile = false) {
+ private function redirectToFile(int $fileId, ?string $openDetails = null, ?string $openFile = null): RedirectResponse {
$uid = $this->userSession->getUser()->getUID();
$baseFolder = $this->rootFolder->getUserFolder($uid);
- $files = $baseFolder->getById($fileId);
- $params = [];
+ $node = $baseFolder->getFirstNodeById($fileId);
+ $params = ['view' => 'files'];
- if (empty($files) && $this->appManager->isEnabledForUser('files_trashbin')) {
- $baseFolder = $this->rootFolder->get($uid . '/files_trashbin/files/');
- $files = $baseFolder->getById($fileId);
- $params['view'] = 'trashbin';
+ try {
+ $this->redirectToFileIfInTrashbin($fileId);
+ } catch (NotFoundException $e) {
}
- if (!empty($files)) {
- $file = current($files);
- if ($file instanceof Folder) {
+ if ($node) {
+ $params['fileid'] = $fileId;
+ if ($node instanceof Folder) {
// set the full path to enter the folder
- $params['dir'] = $baseFolder->getRelativePath($file->getPath());
+ $params['dir'] = $baseFolder->getRelativePath($node->getPath());
} else {
// set parent path as dir
- $params['dir'] = $baseFolder->getRelativePath($file->getParent()->getPath());
- // and scroll to the entry
- $params['scrollto'] = $file->getName();
+ $params['dir'] = $baseFolder->getRelativePath($node->getParent()->getPath());
+ // open the file by default (opening the viewer)
+ $params['openfile'] = 'true';
+ }
- if ($setOpenfile) {
- // forward the openfile URL parameter.
- $params['openfile'] = $fileId;
- }
+ // Forward open parameters if any.
+ // - openfile is true by default
+ // - opendetails is undefined by default
+ // - both will be evaluated as truthy
+ if ($openDetails !== null) {
+ $params['opendetails'] = $openDetails !== 'false' ? 'true' : 'false';
}
- return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.index', $params));
+ if ($openFile !== null) {
+ $params['openfile'] = $openFile !== 'false' ? 'true' : 'false';
+ }
+
+ return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.indexViewFileid', $params));
}
- throw new \OCP\Files\NotFoundException();
+
+ throw new NotFoundException();
}
}