From df00242244c1a38c9e21b2bda538fed88e5b3f55 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Mon, 13 Apr 2026 16:06:35 +0200 Subject: [PATCH 1/2] feat(files_versions): allow to block version creation using WFE This allows users to create workflows to block the versions creation for some files, based on tags or other conditions using the workflow engine. The usecase would be compliance to allow configure some rules to prevent versioning. Signed-off-by: Ferdinand Thiessen --- .../composer/composer/autoload_classmap.php | 3 + .../composer/composer/autoload_static.php | 3 + .../lib/AppInfo/Application.php | 11 ++- .../lib/BlockVersioningOperation.php | 95 +++++++++++++++++++ .../CreateVersionListenerForWorkflow.php | 38 ++++++++ .../RegisterWorkflowIntegrationListener.php | 34 +++++++ apps/files_versions/src/workflow.ts | 12 +++ build/frontend/vite.config.ts | 1 + 8 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 apps/files_versions/lib/BlockVersioningOperation.php create mode 100644 apps/files_versions/lib/Listener/CreateVersionListenerForWorkflow.php create mode 100644 apps/files_versions/lib/Listener/RegisterWorkflowIntegrationListener.php create mode 100644 apps/files_versions/src/workflow.ts diff --git a/apps/files_versions/composer/composer/autoload_classmap.php b/apps/files_versions/composer/composer/autoload_classmap.php index 27e68decdcc52..22f7174395ff0 100644 --- a/apps/files_versions/composer/composer/autoload_classmap.php +++ b/apps/files_versions/composer/composer/autoload_classmap.php @@ -20,13 +20,16 @@ 'OCA\\Files_Versions\\Events\\VersionCreatedEvent' => $baseDir . '/../lib/Events/VersionCreatedEvent.php', 'OCA\\Files_Versions\\Events\\VersionRestoredEvent' => $baseDir . '/../lib/Events/VersionRestoredEvent.php', 'OCA\\Files_Versions\\Expiration' => $baseDir . '/../lib/Expiration.php', + 'OCA\\Files_Versions\\Listener\\CreateVersionListenerForWorkflow' => $baseDir . '/../lib/Listener/CreateVersionListenerForWorkflow.php', 'OCA\\Files_Versions\\Listener\\FileEventsListener' => $baseDir . '/../lib/Listener/FileEventsListener.php', 'OCA\\Files_Versions\\Listener\\LegacyRollbackListener' => $baseDir . '/../lib/Listener/LegacyRollbackListener.php', 'OCA\\Files_Versions\\Listener\\LoadAdditionalListener' => $baseDir . '/../lib/Listener/LoadAdditionalListener.php', 'OCA\\Files_Versions\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php', + 'OCA\\Files_Versions\\Listener\\RegisterWorkflowIntegrationListener' => $baseDir . '/../lib/Listener/RegisterWorkflowIntegrationListener.php', 'OCA\\Files_Versions\\Listener\\VersionAuthorListener' => $baseDir . '/../lib/Listener/VersionAuthorListener.php', 'OCA\\Files_Versions\\Listener\\VersionStorageMoveListener' => $baseDir . '/../lib/Listener/VersionStorageMoveListener.php', 'OCA\\Files_Versions\\Migration\\Version1020Date20221114144058' => $baseDir . '/../lib/Migration/Version1020Date20221114144058.php', + 'OCA\\Files_Versions\\Operation' => $baseDir . '/../lib/Operation.php', 'OCA\\Files_Versions\\Sabre\\Plugin' => $baseDir . '/../lib/Sabre/Plugin.php', 'OCA\\Files_Versions\\Sabre\\RestoreFolder' => $baseDir . '/../lib/Sabre/RestoreFolder.php', 'OCA\\Files_Versions\\Sabre\\RootCollection' => $baseDir . '/../lib/Sabre/RootCollection.php', diff --git a/apps/files_versions/composer/composer/autoload_static.php b/apps/files_versions/composer/composer/autoload_static.php index 060c062c9f6cc..5c9867c42aec5 100644 --- a/apps/files_versions/composer/composer/autoload_static.php +++ b/apps/files_versions/composer/composer/autoload_static.php @@ -35,13 +35,16 @@ class ComposerStaticInitFiles_Versions 'OCA\\Files_Versions\\Events\\VersionCreatedEvent' => __DIR__ . '/..' . '/../lib/Events/VersionCreatedEvent.php', 'OCA\\Files_Versions\\Events\\VersionRestoredEvent' => __DIR__ . '/..' . '/../lib/Events/VersionRestoredEvent.php', 'OCA\\Files_Versions\\Expiration' => __DIR__ . '/..' . '/../lib/Expiration.php', + 'OCA\\Files_Versions\\Listener\\CreateVersionListenerForWorkflow' => __DIR__ . '/..' . '/../lib/Listener/CreateVersionListenerForWorkflow.php', 'OCA\\Files_Versions\\Listener\\FileEventsListener' => __DIR__ . '/..' . '/../lib/Listener/FileEventsListener.php', 'OCA\\Files_Versions\\Listener\\LegacyRollbackListener' => __DIR__ . '/..' . '/../lib/Listener/LegacyRollbackListener.php', 'OCA\\Files_Versions\\Listener\\LoadAdditionalListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalListener.php', 'OCA\\Files_Versions\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php', + 'OCA\\Files_Versions\\Listener\\RegisterWorkflowIntegrationListener' => __DIR__ . '/..' . '/../lib/Listener/RegisterWorkflowIntegrationListener.php', 'OCA\\Files_Versions\\Listener\\VersionAuthorListener' => __DIR__ . '/..' . '/../lib/Listener/VersionAuthorListener.php', 'OCA\\Files_Versions\\Listener\\VersionStorageMoveListener' => __DIR__ . '/..' . '/../lib/Listener/VersionStorageMoveListener.php', 'OCA\\Files_Versions\\Migration\\Version1020Date20221114144058' => __DIR__ . '/..' . '/../lib/Migration/Version1020Date20221114144058.php', + 'OCA\\Files_Versions\\Operation' => __DIR__ . '/..' . '/../lib/Operation.php', 'OCA\\Files_Versions\\Sabre\\Plugin' => __DIR__ . '/..' . '/../lib/Sabre/Plugin.php', 'OCA\\Files_Versions\\Sabre\\RestoreFolder' => __DIR__ . '/..' . '/../lib/Sabre/RestoreFolder.php', 'OCA\\Files_Versions\\Sabre\\RootCollection' => __DIR__ . '/..' . '/../lib/Sabre/RootCollection.php', diff --git a/apps/files_versions/lib/AppInfo/Application.php b/apps/files_versions/lib/AppInfo/Application.php index 6d1aa6c21baf3..3f76d27ae5644 100644 --- a/apps/files_versions/lib/AppInfo/Application.php +++ b/apps/files_versions/lib/AppInfo/Application.php @@ -11,11 +11,14 @@ use OCA\Files\Event\LoadAdditionalScriptsEvent; use OCA\Files\Event\LoadSidebar; use OCA\Files_Versions\Capabilities; +use OCA\Files_Versions\Events\CreateVersionEvent; use OCA\Files_Versions\Events\VersionRestoredEvent; +use OCA\Files_Versions\Listener\CreateVersionListenerForWorkflow; use OCA\Files_Versions\Listener\FileEventsListener; use OCA\Files_Versions\Listener\LegacyRollbackListener; use OCA\Files_Versions\Listener\LoadAdditionalListener; use OCA\Files_Versions\Listener\LoadSidebarListener; +use OCA\Files_Versions\Listener\RegisterWorkflowIntegrationListener; use OCA\Files_Versions\Listener\VersionAuthorListener; use OCA\Files_Versions\Listener\VersionStorageMoveListener; use OCA\Files_Versions\Versions\IVersionManager; @@ -36,6 +39,8 @@ use OCP\Files\Events\Node\NodeRenamedEvent; use OCP\Files\Events\Node\NodeTouchedEvent; use OCP\Files\Events\Node\NodeWrittenEvent; +use OCP\WorkflowEngine\Events\LoadSettingsScriptsEvent; +use OCP\WorkflowEngine\Events\RegisterOperationsEvent; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -85,8 +90,12 @@ public function register(IRegistrationContext $context): void { // we add the version author listener with lower priority to make sure new versions already are created by FileEventsListener $context->registerEventListener(NodeWrittenEvent::class, VersionAuthorListener::class, -1); - $context->registerEventListener(VersionRestoredEvent::class, LegacyRollbackListener::class); + + // WFE integration + $context->registerEventListener(RegisterOperationsEvent::class, RegisterWorkflowIntegrationListener::class); + $context->registerEventListener(LoadSettingsScriptsEvent::class, RegisterWorkflowIntegrationListener::class); + $context->registerEventListener(CreateVersionEvent::class, CreateVersionListenerForWorkflow::class); } #[\Override] diff --git a/apps/files_versions/lib/BlockVersioningOperation.php b/apps/files_versions/lib/BlockVersioningOperation.php new file mode 100644 index 0000000000000..cfe0fd178f58b --- /dev/null +++ b/apps/files_versions/lib/BlockVersioningOperation.php @@ -0,0 +1,95 @@ +l10n->t('Block file versioning'); + } + + #[\Override] + public function getDescription(): string { + return $this->l10n->t('Automatic tag based blocking of file version creation.'); + } + + #[\Override] + public function getIcon(): string { + return $this->urlGenerator->imagePath('files_versions', 'app.svg'); + } + + #[\Override] + public function isAvailableForScope(int $scope): bool { + return $scope === IManager::SCOPE_ADMIN; + } + + #[\Override] + public function validateOperation(string $name, array $checks, string $operation): void { + if (empty($checks)) { + throw new \UnexpectedValueException($this->l10n->t('No rule given')); + } + } + + #[\Override] + public function onEvent(string $eventName, Event $event, IRuleMatcher $ruleMatcher): void { + if ($eventName !== CreateVersionEvent::class || !($event instanceof CreateVersionEvent)) { + return; + } + + $node = $event->getNode(); + $path = $node->getInternalPath(); + + $ruleMatcher->setFileInfo( + $node->getStorage(), + $path, + $node instanceof Folder, + ); + $ruleMatcher->setEntitySubject($this->fileEntity, $node); + $ruleMatcher->setOperation($this); + $flows = $ruleMatcher->getFlows(); + + if ($flows !== []) { + $this->logger->debug('Blocking version creation due to matching workflow rules', [ + 'path' => $path, + ]); + $event->disableVersions(); + } + } + + #[\Override] + public function getTriggerHint(): string { + return $this->l10n->t('A new version is created'); // TRANSLATORS: This will be shown as "When: " "A new version is created" + } +} diff --git a/apps/files_versions/lib/Listener/CreateVersionListenerForWorkflow.php b/apps/files_versions/lib/Listener/CreateVersionListenerForWorkflow.php new file mode 100644 index 0000000000000..3723d4911ea23 --- /dev/null +++ b/apps/files_versions/lib/Listener/CreateVersionListenerForWorkflow.php @@ -0,0 +1,38 @@ + */ +class CreateVersionListenerForWorkflow implements IEventListener { + + public function __construct( + private IManager $manager, + private BlockVersioningOperation $operation, + ) { + } + + #[\Override] + public function handle(Event $event): void { + if (!($event instanceof CreateVersionEvent)) { + return; + } + + $this->operation->onEvent( + $event::class, + $event, + $this->manager->getRuleMatcher(), + ); + } +} diff --git a/apps/files_versions/lib/Listener/RegisterWorkflowIntegrationListener.php b/apps/files_versions/lib/Listener/RegisterWorkflowIntegrationListener.php new file mode 100644 index 0000000000000..77eb8a145b8a2 --- /dev/null +++ b/apps/files_versions/lib/Listener/RegisterWorkflowIntegrationListener.php @@ -0,0 +1,34 @@ + */ +class RegisterWorkflowIntegrationListener implements IEventListener { + + public function __construct( + private readonly BlockVersioningOperation $operation, + ) { + } + + #[\Override] + public function handle(Event $event): void { + if ($event instanceof RegisterOperationsEvent) { + $event->registerOperation($this->operation); + } elseif ($event instanceof LoadSettingsScriptsEvent) { + Util::addScript('files_versions', 'workflow', 'workflowengine'); + } + } +} diff --git a/apps/files_versions/src/workflow.ts b/apps/files_versions/src/workflow.ts new file mode 100644 index 0000000000000..f3ffe6b6c8c08 --- /dev/null +++ b/apps/files_versions/src/workflow.ts @@ -0,0 +1,12 @@ +/*! + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +window.addEventListener('DOMContentLoaded', () => { + globalThis.OCA.WorkflowEngine.registerOperator({ + id: 'OCA\\Files_Versions\\BlockVersioningOperation', + color: '#ff5900', + operation: 'deny', + }) +}) diff --git a/build/frontend/vite.config.ts b/build/frontend/vite.config.ts index 0e0a924b8a231..c07ff0359b7c1 100644 --- a/build/frontend/vite.config.ts +++ b/build/frontend/vite.config.ts @@ -48,6 +48,7 @@ const modules = { }, files_versions: { 'sidebar-tab': resolve(import.meta.dirname, 'apps/files_versions/src', 'sidebar_tab.ts'), + workflow: resolve(import.meta.dirname, 'apps/files_versions/src', 'workflow.ts'), }, oauth2: { 'settings-admin': resolve(import.meta.dirname, 'apps/oauth2/src', 'settings-admin.ts'), From 761b330dc223b1267e46281a7b14ba41d5a551bb Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Mon, 13 Apr 2026 16:11:31 +0200 Subject: [PATCH 2/2] fix(files_version): dispatch `CreateVersionEvent` as typed event Signed-off-by: Ferdinand Thiessen --- apps/files_versions/composer/composer/autoload_classmap.php | 2 +- apps/files_versions/composer/composer/autoload_static.php | 2 +- apps/files_versions/lib/Storage.php | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/files_versions/composer/composer/autoload_classmap.php b/apps/files_versions/composer/composer/autoload_classmap.php index 22f7174395ff0..c66020648df81 100644 --- a/apps/files_versions/composer/composer/autoload_classmap.php +++ b/apps/files_versions/composer/composer/autoload_classmap.php @@ -9,6 +9,7 @@ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'OCA\\Files_Versions\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php', 'OCA\\Files_Versions\\BackgroundJob\\ExpireVersions' => $baseDir . '/../lib/BackgroundJob/ExpireVersions.php', + 'OCA\\Files_Versions\\BlockVersioningOperation' => $baseDir . '/../lib/BlockVersioningOperation.php', 'OCA\\Files_Versions\\Capabilities' => $baseDir . '/../lib/Capabilities.php', 'OCA\\Files_Versions\\Command\\CleanUp' => $baseDir . '/../lib/Command/CleanUp.php', 'OCA\\Files_Versions\\Command\\Expire' => $baseDir . '/../lib/Command/Expire.php', @@ -29,7 +30,6 @@ 'OCA\\Files_Versions\\Listener\\VersionAuthorListener' => $baseDir . '/../lib/Listener/VersionAuthorListener.php', 'OCA\\Files_Versions\\Listener\\VersionStorageMoveListener' => $baseDir . '/../lib/Listener/VersionStorageMoveListener.php', 'OCA\\Files_Versions\\Migration\\Version1020Date20221114144058' => $baseDir . '/../lib/Migration/Version1020Date20221114144058.php', - 'OCA\\Files_Versions\\Operation' => $baseDir . '/../lib/Operation.php', 'OCA\\Files_Versions\\Sabre\\Plugin' => $baseDir . '/../lib/Sabre/Plugin.php', 'OCA\\Files_Versions\\Sabre\\RestoreFolder' => $baseDir . '/../lib/Sabre/RestoreFolder.php', 'OCA\\Files_Versions\\Sabre\\RootCollection' => $baseDir . '/../lib/Sabre/RootCollection.php', diff --git a/apps/files_versions/composer/composer/autoload_static.php b/apps/files_versions/composer/composer/autoload_static.php index 5c9867c42aec5..0d2673c4fcf8d 100644 --- a/apps/files_versions/composer/composer/autoload_static.php +++ b/apps/files_versions/composer/composer/autoload_static.php @@ -24,6 +24,7 @@ class ComposerStaticInitFiles_Versions 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'OCA\\Files_Versions\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php', 'OCA\\Files_Versions\\BackgroundJob\\ExpireVersions' => __DIR__ . '/..' . '/../lib/BackgroundJob/ExpireVersions.php', + 'OCA\\Files_Versions\\BlockVersioningOperation' => __DIR__ . '/..' . '/../lib/BlockVersioningOperation.php', 'OCA\\Files_Versions\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php', 'OCA\\Files_Versions\\Command\\CleanUp' => __DIR__ . '/..' . '/../lib/Command/CleanUp.php', 'OCA\\Files_Versions\\Command\\Expire' => __DIR__ . '/..' . '/../lib/Command/Expire.php', @@ -44,7 +45,6 @@ class ComposerStaticInitFiles_Versions 'OCA\\Files_Versions\\Listener\\VersionAuthorListener' => __DIR__ . '/..' . '/../lib/Listener/VersionAuthorListener.php', 'OCA\\Files_Versions\\Listener\\VersionStorageMoveListener' => __DIR__ . '/..' . '/../lib/Listener/VersionStorageMoveListener.php', 'OCA\\Files_Versions\\Migration\\Version1020Date20221114144058' => __DIR__ . '/..' . '/../lib/Migration/Version1020Date20221114144058.php', - 'OCA\\Files_Versions\\Operation' => __DIR__ . '/..' . '/../lib/Operation.php', 'OCA\\Files_Versions\\Sabre\\Plugin' => __DIR__ . '/..' . '/../lib/Sabre/Plugin.php', 'OCA\\Files_Versions\\Sabre\\RestoreFolder' => __DIR__ . '/..' . '/../lib/Sabre/RestoreFolder.php', 'OCA\\Files_Versions\\Sabre\\RootCollection' => __DIR__ . '/..' . '/../lib/Sabre/RootCollection.php', diff --git a/apps/files_versions/lib/Storage.php b/apps/files_versions/lib/Storage.php index fe9f91534fa01..eaa242e98e138 100644 --- a/apps/files_versions/lib/Storage.php +++ b/apps/files_versions/lib/Storage.php @@ -205,6 +205,7 @@ public static function store($filename) { } $event = new CreateVersionEvent($file); + $eventDispatcher->dispatchTyped($event); $eventDispatcher->dispatch('OCA\Files_Versions::createVersion', $event); if ($event->shouldCreateVersion() === false) { return false;