diff options
author | provokateurin <kate@provokateurin.de> | 2025-01-07 17:02:43 +0100 |
---|---|---|
committer | provokateurin <kate@provokateurin.de> | 2025-01-13 15:19:19 +0100 |
commit | 31c21c779723fb6efbe6aa89b2093091c5d3e187 (patch) | |
tree | 08907a7eb60d849ec9ae72d0d894d0be8496b4c6 /apps/files_trashbin | |
parent | 26fa4da8c2fe461dd73fd92a5c593eecfc6605e5 (diff) | |
download | nextcloud-server-31c21c779723fb6efbe6aa89b2093091c5d3e187.tar.gz nextcloud-server-31c21c779723fb6efbe6aa89b2093091c5d3e187.zip |
feat(files_trashbin): Allow preventing trash to be deleted permanently
Signed-off-by: provokateurin <kate@provokateurin.de>
Diffstat (limited to 'apps/files_trashbin')
12 files changed, 118 insertions, 5 deletions
diff --git a/apps/files_trashbin/composer/composer/autoload_classmap.php b/apps/files_trashbin/composer/composer/autoload_classmap.php index 4ff7c561748..23e2e7baff6 100644 --- a/apps/files_trashbin/composer/composer/autoload_classmap.php +++ b/apps/files_trashbin/composer/composer/autoload_classmap.php @@ -23,6 +23,7 @@ return array( 'OCA\\Files_Trashbin\\Expiration' => $baseDir . '/../lib/Expiration.php', 'OCA\\Files_Trashbin\\Helper' => $baseDir . '/../lib/Helper.php', 'OCA\\Files_Trashbin\\Listener\\EventListener' => $baseDir . '/../lib/Listener/EventListener.php', + 'OCA\\Files_Trashbin\\Listeners\\BeforeTemplateRendered' => $baseDir . '/../lib/Listeners/BeforeTemplateRendered.php', 'OCA\\Files_Trashbin\\Listeners\\LoadAdditionalScripts' => $baseDir . '/../lib/Listeners/LoadAdditionalScripts.php', 'OCA\\Files_Trashbin\\Listeners\\SyncLivePhotosListener' => $baseDir . '/../lib/Listeners/SyncLivePhotosListener.php', 'OCA\\Files_Trashbin\\Migration\\Version1010Date20200630192639' => $baseDir . '/../lib/Migration/Version1010Date20200630192639.php', @@ -40,6 +41,7 @@ return array( 'OCA\\Files_Trashbin\\Sabre\\TrashHome' => $baseDir . '/../lib/Sabre/TrashHome.php', 'OCA\\Files_Trashbin\\Sabre\\TrashRoot' => $baseDir . '/../lib/Sabre/TrashRoot.php', 'OCA\\Files_Trashbin\\Sabre\\TrashbinPlugin' => $baseDir . '/../lib/Sabre/TrashbinPlugin.php', + 'OCA\\Files_Trashbin\\Service\\ConfigService' => $baseDir . '/../lib/Service/ConfigService.php', 'OCA\\Files_Trashbin\\Storage' => $baseDir . '/../lib/Storage.php', 'OCA\\Files_Trashbin\\Trash\\BackendNotFoundException' => $baseDir . '/../lib/Trash/BackendNotFoundException.php', 'OCA\\Files_Trashbin\\Trash\\ITrashBackend' => $baseDir . '/../lib/Trash/ITrashBackend.php', diff --git a/apps/files_trashbin/composer/composer/autoload_static.php b/apps/files_trashbin/composer/composer/autoload_static.php index 4b09239aa1e..fc604299261 100644 --- a/apps/files_trashbin/composer/composer/autoload_static.php +++ b/apps/files_trashbin/composer/composer/autoload_static.php @@ -38,6 +38,7 @@ class ComposerStaticInitFiles_Trashbin 'OCA\\Files_Trashbin\\Expiration' => __DIR__ . '/..' . '/../lib/Expiration.php', 'OCA\\Files_Trashbin\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php', 'OCA\\Files_Trashbin\\Listener\\EventListener' => __DIR__ . '/..' . '/../lib/Listener/EventListener.php', + 'OCA\\Files_Trashbin\\Listeners\\BeforeTemplateRendered' => __DIR__ . '/..' . '/../lib/Listeners/BeforeTemplateRendered.php', 'OCA\\Files_Trashbin\\Listeners\\LoadAdditionalScripts' => __DIR__ . '/..' . '/../lib/Listeners/LoadAdditionalScripts.php', 'OCA\\Files_Trashbin\\Listeners\\SyncLivePhotosListener' => __DIR__ . '/..' . '/../lib/Listeners/SyncLivePhotosListener.php', 'OCA\\Files_Trashbin\\Migration\\Version1010Date20200630192639' => __DIR__ . '/..' . '/../lib/Migration/Version1010Date20200630192639.php', @@ -55,6 +56,7 @@ class ComposerStaticInitFiles_Trashbin 'OCA\\Files_Trashbin\\Sabre\\TrashHome' => __DIR__ . '/..' . '/../lib/Sabre/TrashHome.php', 'OCA\\Files_Trashbin\\Sabre\\TrashRoot' => __DIR__ . '/..' . '/../lib/Sabre/TrashRoot.php', 'OCA\\Files_Trashbin\\Sabre\\TrashbinPlugin' => __DIR__ . '/..' . '/../lib/Sabre/TrashbinPlugin.php', + 'OCA\\Files_Trashbin\\Service\\ConfigService' => __DIR__ . '/..' . '/../lib/Service/ConfigService.php', 'OCA\\Files_Trashbin\\Storage' => __DIR__ . '/..' . '/../lib/Storage.php', 'OCA\\Files_Trashbin\\Trash\\BackendNotFoundException' => __DIR__ . '/..' . '/../lib/Trash/BackendNotFoundException.php', 'OCA\\Files_Trashbin\\Trash\\ITrashBackend' => __DIR__ . '/..' . '/../lib/Trash/ITrashBackend.php', diff --git a/apps/files_trashbin/lib/AppInfo/Application.php b/apps/files_trashbin/lib/AppInfo/Application.php index 115e6e418b4..000677de96c 100644 --- a/apps/files_trashbin/lib/AppInfo/Application.php +++ b/apps/files_trashbin/lib/AppInfo/Application.php @@ -8,10 +8,12 @@ namespace OCA\Files_Trashbin\AppInfo; use OCA\DAV\Connector\Sabre\Principal; use OCA\Files\Event\LoadAdditionalScriptsEvent; +use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent; use OCA\Files_Trashbin\Capabilities; use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent; use OCA\Files_Trashbin\Expiration; use OCA\Files_Trashbin\Listener\EventListener; +use OCA\Files_Trashbin\Listeners\BeforeTemplateRendered; use OCA\Files_Trashbin\Listeners\LoadAdditionalScripts; use OCA\Files_Trashbin\Listeners\SyncLivePhotosListener; use OCA\Files_Trashbin\Trash\ITrashManager; @@ -52,6 +54,11 @@ class Application extends App implements IBootstrap { LoadAdditionalScripts::class ); + $context->registerEventListener( + BeforeTemplateRenderedEvent::class, + BeforeTemplateRendered::class + ); + $context->registerEventListener(BeforeNodeRestoredEvent::class, SyncLivePhotosListener::class); $context->registerEventListener(NodeWrittenEvent::class, EventListener::class); diff --git a/apps/files_trashbin/lib/Capabilities.php b/apps/files_trashbin/lib/Capabilities.php index 863d3692fb6..62be7bcb1a1 100644 --- a/apps/files_trashbin/lib/Capabilities.php +++ b/apps/files_trashbin/lib/Capabilities.php @@ -6,6 +6,7 @@ */ namespace OCA\Files_Trashbin; +use OCA\Files_Trashbin\Service\ConfigService; use OCP\Capabilities\ICapability; /** @@ -18,12 +19,18 @@ class Capabilities implements ICapability { /** * Return this classes capabilities * - * @return array{files: array{undelete: bool}} + * @return array{ + * files: array{ + * undelete: bool, + * delete_from_trash: bool + * } + * } */ public function getCapabilities() { return [ 'files' => [ - 'undelete' => true + 'undelete' => true, + 'delete_from_trash' => ConfigService::getDeleteFromTrashEnabled(), ] ]; } diff --git a/apps/files_trashbin/lib/Listeners/BeforeTemplateRendered.php b/apps/files_trashbin/lib/Listeners/BeforeTemplateRendered.php new file mode 100644 index 00000000000..d62618583f7 --- /dev/null +++ b/apps/files_trashbin/lib/Listeners/BeforeTemplateRendered.php @@ -0,0 +1,32 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files_Trashbin\Listeners; + +use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent; +use OCA\Files_Trashbin\Service\ConfigService; +use OCP\AppFramework\Services\IInitialState; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; + +/** @template-implements IEventListener<BeforeTemplateRenderedEvent> */ +class BeforeTemplateRendered implements IEventListener { + public function __construct( + private IInitialState $initialState, + ) { + } + + public function handle(Event $event): void { + if (!($event instanceof BeforeTemplateRenderedEvent)) { + return; + } + + ConfigService::injectInitialState($this->initialState); + } +} diff --git a/apps/files_trashbin/lib/Listeners/LoadAdditionalScripts.php b/apps/files_trashbin/lib/Listeners/LoadAdditionalScripts.php index 4bfa6bbef3f..7940b934ace 100644 --- a/apps/files_trashbin/lib/Listeners/LoadAdditionalScripts.php +++ b/apps/files_trashbin/lib/Listeners/LoadAdditionalScripts.php @@ -10,17 +10,26 @@ namespace OCA\Files_Trashbin\Listeners; use OCA\Files\Event\LoadAdditionalScriptsEvent; use OCA\Files_Trashbin\AppInfo\Application; +use OCA\Files_Trashbin\Service\ConfigService; +use OCP\AppFramework\Services\IInitialState; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\Util; /** @template-implements IEventListener<LoadAdditionalScriptsEvent> */ class LoadAdditionalScripts implements IEventListener { + public function __construct( + private IInitialState $initialState, + ) { + } + public function handle(Event $event): void { if (!($event instanceof LoadAdditionalScriptsEvent)) { return; } Util::addInitScript(Application::APP_ID, 'init'); + + ConfigService::injectInitialState($this->initialState); } } diff --git a/apps/files_trashbin/lib/Sabre/AbstractTrash.php b/apps/files_trashbin/lib/Sabre/AbstractTrash.php index 343c8b6be6d..f032395437b 100644 --- a/apps/files_trashbin/lib/Sabre/AbstractTrash.php +++ b/apps/files_trashbin/lib/Sabre/AbstractTrash.php @@ -8,10 +8,12 @@ declare(strict_types=1); */ namespace OCA\Files_Trashbin\Sabre; +use OCA\Files_Trashbin\Service\ConfigService; use OCA\Files_Trashbin\Trash\ITrashItem; use OCA\Files_Trashbin\Trash\ITrashManager; use OCP\Files\FileInfo; use OCP\IUser; +use Sabre\DAV\Exception\Forbidden; abstract class AbstractTrash implements ITrash { public function __construct( @@ -73,6 +75,10 @@ abstract class AbstractTrash implements ITrash { } public function delete() { + if (!ConfigService::getDeleteFromTrashEnabled()) { + throw new Forbidden('Not allowed to delete items from the trash bin'); + } + $this->trashManager->removeItem($this->data); } diff --git a/apps/files_trashbin/lib/Sabre/TrashRoot.php b/apps/files_trashbin/lib/Sabre/TrashRoot.php index d6a4f5cc67e..dd89583d9a1 100644 --- a/apps/files_trashbin/lib/Sabre/TrashRoot.php +++ b/apps/files_trashbin/lib/Sabre/TrashRoot.php @@ -8,6 +8,7 @@ declare(strict_types=1); */ namespace OCA\Files_Trashbin\Sabre; +use OCA\Files_Trashbin\Service\ConfigService; use OCA\Files_Trashbin\Trash\ITrashItem; use OCA\Files_Trashbin\Trash\ITrashManager; use OCA\Files_Trashbin\Trashbin; @@ -26,6 +27,10 @@ class TrashRoot implements ICollection { } public function delete() { + if (!ConfigService::getDeleteFromTrashEnabled()) { + throw new Forbidden('Not allowed to delete items from the trash bin'); + } + Trashbin::deleteAll(); foreach ($this->trashManager->listTrashRoot($this->user) as $trashItem) { $this->trashManager->removeItem($trashItem); diff --git a/apps/files_trashbin/lib/Service/ConfigService.php b/apps/files_trashbin/lib/Service/ConfigService.php new file mode 100644 index 00000000000..9e7826fe580 --- /dev/null +++ b/apps/files_trashbin/lib/Service/ConfigService.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +namespace OCA\Files_Trashbin\Service; + +use OCP\AppFramework\Services\IInitialState; +use OCP\IConfig; +use OCP\Server; + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +class ConfigService { + public static function getDeleteFromTrashEnabled(): bool { + return Server::get(IConfig::class)->getSystemValueBool('files.trash.delete', true); + } + + public static function injectInitialState(IInitialState $initialState): void { + $initialState->provideLazyInitialState('config', function () { + return [ + 'allow_delete' => ConfigService::getDeleteFromTrashEnabled(), + ]; + }); + } +} diff --git a/apps/files_trashbin/openapi.json b/apps/files_trashbin/openapi.json index 6662fe10815..716d34db641 100644 --- a/apps/files_trashbin/openapi.json +++ b/apps/files_trashbin/openapi.json @@ -29,11 +29,15 @@ "files": { "type": "object", "required": [ - "undelete" + "undelete", + "delete_from_trash" ], "properties": { "undelete": { "type": "boolean" + }, + "delete_from_trash": { + "type": "boolean" } } } diff --git a/apps/files_trashbin/src/fileListActions/emptyTrashAction.ts b/apps/files_trashbin/src/fileListActions/emptyTrashAction.ts index 25e3083abb9..a77a4ad89f3 100644 --- a/apps/files_trashbin/src/fileListActions/emptyTrashAction.ts +++ b/apps/files_trashbin/src/fileListActions/emptyTrashAction.ts @@ -19,6 +19,11 @@ import { logger } from '../logger.ts' import { generateRemoteUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' import { emit } from '@nextcloud/event-bus' +import { loadState } from '@nextcloud/initial-state' + +export type FilesTrashbinConfigState = { + allow_delete: boolean; +} const emptyTrash = async (): Promise<boolean> => { try { @@ -42,6 +47,12 @@ export const emptyTrashAction = new FileListAction({ if (view.id !== 'trashbin') { return false } + + const config = loadState<FilesTrashbinConfigState>('files_trashbin', 'config') + if (!config.allow_delete) { + return false + } + return nodes.length > 0 && folder.path === '/' }, diff --git a/apps/files_trashbin/tests/CapabilitiesTest.php b/apps/files_trashbin/tests/CapabilitiesTest.php index a2a17ca349e..a5e4e79aefd 100644 --- a/apps/files_trashbin/tests/CapabilitiesTest.php +++ b/apps/files_trashbin/tests/CapabilitiesTest.php @@ -17,11 +17,12 @@ class CapabilitiesTest extends TestCase { parent::setUp(); $this->capabilities = new Capabilities(); } - + public function testGetCapabilities(): void { $capabilities = [ 'files' => [ - 'undelete' => true + 'undelete' => true, + 'delete_from_trash' => true, ] ]; |