diff --git a/apps/workflowengine/appinfo/info.xml b/apps/workflowengine/appinfo/info.xml index c6fce1d533439..ff32c274991b4 100644 --- a/apps/workflowengine/appinfo/info.xml +++ b/apps/workflowengine/appinfo/info.xml @@ -9,7 +9,7 @@ Nextcloud workflow engine Nextcloud workflow engine Nextcloud workflow engine - 2.16.0 + 2.16.1 agpl Arthur Schiwon Julius Härtl diff --git a/apps/workflowengine/composer/composer/autoload_classmap.php b/apps/workflowengine/composer/composer/autoload_classmap.php index 0fd869905d4ee..de872ff9fa0a6 100644 --- a/apps/workflowengine/composer/composer/autoload_classmap.php +++ b/apps/workflowengine/composer/composer/autoload_classmap.php @@ -33,6 +33,7 @@ 'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => $baseDir . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php', 'OCA\\WorkflowEngine\\Migration\\Version2000Date20190808074233' => $baseDir . '/../lib/Migration/Version2000Date20190808074233.php', 'OCA\\WorkflowEngine\\Migration\\Version2200Date20210805101925' => $baseDir . '/../lib/Migration/Version2200Date20210805101925.php', + 'OCA\\WorkflowEngine\\Migration\\Version3400Date20260227000000' => $baseDir . '/../lib/Migration/Version3400Date20260227000000.php', 'OCA\\WorkflowEngine\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php', 'OCA\\WorkflowEngine\\Service\\Logger' => $baseDir . '/../lib/Service/Logger.php', 'OCA\\WorkflowEngine\\Service\\RuleMatcher' => $baseDir . '/../lib/Service/RuleMatcher.php', diff --git a/apps/workflowengine/composer/composer/autoload_static.php b/apps/workflowengine/composer/composer/autoload_static.php index dddcf5611dab1..35129e00e51c0 100644 --- a/apps/workflowengine/composer/composer/autoload_static.php +++ b/apps/workflowengine/composer/composer/autoload_static.php @@ -48,6 +48,7 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => __DIR__ . '/..' . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php', 'OCA\\WorkflowEngine\\Migration\\Version2000Date20190808074233' => __DIR__ . '/..' . '/../lib/Migration/Version2000Date20190808074233.php', 'OCA\\WorkflowEngine\\Migration\\Version2200Date20210805101925' => __DIR__ . '/..' . '/../lib/Migration/Version2200Date20210805101925.php', + 'OCA\\WorkflowEngine\\Migration\\Version3400Date20260227000000' => __DIR__ . '/..' . '/../lib/Migration/Version3400Date20260227000000.php', 'OCA\\WorkflowEngine\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php', 'OCA\\WorkflowEngine\\Service\\Logger' => __DIR__ . '/..' . '/../lib/Service/Logger.php', 'OCA\\WorkflowEngine\\Service\\RuleMatcher' => __DIR__ . '/..' . '/../lib/Service/RuleMatcher.php', diff --git a/apps/workflowengine/lib/Entity/File.php b/apps/workflowengine/lib/Entity/File.php index 0dc929a325bca..455717ed5482f 100644 --- a/apps/workflowengine/lib/Entity/File.php +++ b/apps/workflowengine/lib/Entity/File.php @@ -9,8 +9,15 @@ namespace OCA\WorkflowEngine\Entity; use OC\Files\Config\UserMountCache; +use OC\SystemTag\Events\SingleTagAssignedEvent; use OCP\EventDispatcher\Event; -use OCP\EventDispatcher\GenericEvent; +use OCP\Files\Events\Node\AbstractNodeEvent; +use OCP\Files\Events\Node\NodeCopiedEvent; +use OCP\Files\Events\Node\NodeCreatedEvent; +use OCP\Files\Events\Node\NodeDeletedEvent; +use OCP\Files\Events\Node\NodeRenamedEvent; +use OCP\Files\Events\Node\NodeTouchedEvent; +use OCP\Files\Events\Node\NodeUpdatedEvent; use OCP\Files\InvalidPathException; use OCP\Files\IRootFolder; use OCP\Files\Mount\IMountManager; @@ -22,7 +29,6 @@ use OCP\IUserManager; use OCP\IUserSession; use OCP\SystemTag\ISystemTagManager; -use OCP\SystemTag\MapperEvent; use OCP\WorkflowEngine\EntityContext\IContextPortation; use OCP\WorkflowEngine\EntityContext\IDisplayText; use OCP\WorkflowEngine\EntityContext\IIcon; @@ -30,50 +36,57 @@ use OCP\WorkflowEngine\GenericEntityEvent; use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IRuleMatcher; +use Override; class File implements IEntity, IDisplayText, IUrl, IIcon, IContextPortation { - private const EVENT_NAMESPACE = '\OCP\Files::'; + /** @var ?class-string $eventName */ protected ?string $eventName = null; protected ?Event $event = null; private ?Node $node = null; private ?IUser $actingUser = null; public function __construct( - protected IL10N $l10n, - protected IURLGenerator $urlGenerator, - protected IRootFolder $root, - private IUserSession $userSession, - private ISystemTagManager $tagManager, - private IUserManager $userManager, - private UserMountCache $userMountCache, - private IMountManager $mountManager, + protected readonly IL10N $l10n, + protected readonly IURLGenerator $urlGenerator, + protected readonly IRootFolder $root, + private readonly IUserSession $userSession, + private readonly ISystemTagManager $tagManager, + private readonly IUserManager $userManager, + private readonly UserMountCache $userMountCache, + private readonly IMountManager $mountManager, ) { } + #[Override] public function getName(): string { return $this->l10n->t('File'); } + #[Override] public function getIcon(): string { return $this->urlGenerator->imagePath('core', 'categories/files.svg'); } + #[Override] public function getEvents(): array { return [ - new GenericEntityEvent($this->l10n->t('File created'), self::EVENT_NAMESPACE . 'postCreate'), - new GenericEntityEvent($this->l10n->t('File updated'), self::EVENT_NAMESPACE . 'postWrite'), - new GenericEntityEvent($this->l10n->t('File renamed'), self::EVENT_NAMESPACE . 'postRename'), - new GenericEntityEvent($this->l10n->t('File deleted'), self::EVENT_NAMESPACE . 'postDelete'), - new GenericEntityEvent($this->l10n->t('File accessed'), self::EVENT_NAMESPACE . 'postTouch'), - new GenericEntityEvent($this->l10n->t('File copied'), self::EVENT_NAMESPACE . 'postCopy'), - new GenericEntityEvent($this->l10n->t('Tag assigned'), MapperEvent::EVENT_ASSIGN), + new GenericEntityEvent($this->l10n->t('File created'), NodeCreatedEvent::class), + new GenericEntityEvent($this->l10n->t('File updated'), NodeUpdatedEvent::class), + new GenericEntityEvent($this->l10n->t('File renamed'), NodeRenamedEvent::class), + new GenericEntityEvent($this->l10n->t('File deleted'), NodeDeletedEvent::class), + new GenericEntityEvent($this->l10n->t('File accessed'), NodeTouchedEvent::class), + new GenericEntityEvent($this->l10n->t('File copied'), NodeCopiedEvent::class), + new GenericEntityEvent($this->l10n->t('Tag assigned'), SingleTagAssignedEvent::class), ]; } + #[Override] public function prepareRuleMatcher(IRuleMatcher $ruleMatcher, string $eventName, Event $event): void { - if (!$event instanceof GenericEvent && !$event instanceof MapperEvent) { + $isSupported = array_any($this->getEvents(), static fn (GenericEntityEvent $genericEvent): bool => is_a($event, $genericEvent->getEventName())); + if (!$isSupported) { return; } + $this->eventName = $eventName; $this->event = $event; $this->actingUser = $this->actingUser ?? $this->userSession->getUser(); @@ -81,11 +94,12 @@ public function prepareRuleMatcher(IRuleMatcher $ruleMatcher, string $eventName, $node = $this->getNode(); $ruleMatcher->setEntitySubject($this, $node); $ruleMatcher->setFileInfo($node->getStorage(), $node->getInternalPath()); - } catch (NotFoundException $e) { + } catch (NotFoundException) { // pass } } + #[Override] public function isLegitimatedForUserId(string $userId): bool { try { $node = $this->getNode(); @@ -93,7 +107,7 @@ public function isLegitimatedForUserId(string $userId): bool { return true; } - if ($this->eventName === self::EVENT_NAMESPACE . 'postDelete') { + if ($this->eventName === NodeDeletedEvent::class) { // At postDelete, the file no longer exists. Check for parent folder instead. $fileId = $node->getParentId(); } else { @@ -120,35 +134,27 @@ protected function getNode(): Node { if ($this->node) { return $this->node; } - if (!$this->event instanceof GenericEvent && !$this->event instanceof MapperEvent) { + + if ($this->event instanceof AbstractNodeEvent) { + return $this->event->getNode(); + } + + if (!$this->event instanceof SingleTagAssignedEvent || $this->event->getObjectType() !== 'files') { throw new NotFoundException(); } - switch ($this->eventName) { - case self::EVENT_NAMESPACE . 'postCreate': - case self::EVENT_NAMESPACE . 'postWrite': - case self::EVENT_NAMESPACE . 'postDelete': - case self::EVENT_NAMESPACE . 'postTouch': - return $this->event->getSubject(); - case self::EVENT_NAMESPACE . 'postRename': - case self::EVENT_NAMESPACE . 'postCopy': - return $this->event->getSubject()[1]; - case MapperEvent::EVENT_ASSIGN: - if (!$this->event instanceof MapperEvent || $this->event->getObjectType() !== 'files') { - throw new NotFoundException(); - } - $this->node = $this->root->getFirstNodeById((int)$this->event->getObjectId()); - if ($this->node !== null) { - return $this->node; - } - break; + + $this->node = $this->root->getFirstNodeById((int)$this->event->getObjectId()); + if ($this->node === null) { + throw new NotFoundException(); } - throw new NotFoundException(); + + return $this->node; } public function getDisplayText(int $verbosity = 0): string { try { $node = $this->getNode(); - } catch (NotFoundException $e) { + } catch (NotFoundException) { return ''; } @@ -158,21 +164,21 @@ public function getDisplayText(int $verbosity = 0): string { ]; switch ($this->eventName) { - case self::EVENT_NAMESPACE . 'postCreate': + case NodeCreatedEvent::class: return $this->l10n->t('%s created %s', $options); - case self::EVENT_NAMESPACE . 'postWrite': + case NodeUpdatedEvent::class: return $this->l10n->t('%s modified %s', $options); - case self::EVENT_NAMESPACE . 'postDelete': + case NodeDeletedEvent::class: return $this->l10n->t('%s deleted %s', $options); - case self::EVENT_NAMESPACE . 'postTouch': + case NodeTouchedEvent::class: return $this->l10n->t('%s accessed %s', $options); - case self::EVENT_NAMESPACE . 'postRename': + case NodeRenamedEvent::class: return $this->l10n->t('%s renamed %s', $options); - case self::EVENT_NAMESPACE . 'postCopy': + case NodeCopiedEvent::class: return $this->l10n->t('%s copied %s', $options); - case MapperEvent::EVENT_ASSIGN: + case SingleTagAssignedEvent::class: $tagNames = []; - if ($this->event instanceof MapperEvent) { + if ($this->event instanceof SingleTagAssignedEvent) { $tagIDs = $this->event->getTags(); $tagObjects = $this->tagManager->getTagsByIds($tagIDs); foreach ($tagObjects as $systemTag) { @@ -201,9 +207,7 @@ public function getUrl(): string { } } - /** - * @inheritDoc - */ + #[Override] public function exportContextIDs(): array { $nodeOwner = $this->getNode()->getOwner(); $actingUserId = null; @@ -215,14 +219,12 @@ public function exportContextIDs(): array { return [ 'eventName' => $this->eventName, 'nodeId' => $this->getNode()->getId(), - 'nodeOwnerId' => $nodeOwner ? $nodeOwner->getUID() : null, + 'nodeOwnerId' => $nodeOwner?->getUID(), 'actingUserId' => $actingUserId, ]; } - /** - * @inheritDoc - */ + #[Override] public function importContextIDs(array $contextIDs): void { $this->eventName = $contextIDs['eventName']; if ($contextIDs['nodeOwnerId'] !== null) { @@ -237,9 +239,7 @@ public function importContextIDs(array $contextIDs): void { } } - /** - * @inheritDoc - */ + #[Override] public function getIconUrl(): string { return $this->getIcon(); } diff --git a/apps/workflowengine/lib/Migration/Version3400Date20260227000000.php b/apps/workflowengine/lib/Migration/Version3400Date20260227000000.php new file mode 100644 index 0000000000000..0c0cfd63458f5 --- /dev/null +++ b/apps/workflowengine/lib/Migration/Version3400Date20260227000000.php @@ -0,0 +1,94 @@ +connection->getQueryBuilder(); + $lastId = null; + while (true) { + $qb->select('*') + ->from('flow_operations'); + if ($lastId !== null) { + $qb->andWhere($qb->expr()->gt('id', $qb->createNamedParameter($lastId))); + } + $qb->setMaxResults(1000); + $newMapping = []; + $result = $qb->executeQuery(); + while ($row = $result->fetchAssociative()) { + $events = json_decode($row['events'], true); + $newEvents = array_map(function (string $eventName): string { + return match ($eventName) { + '\OCP\Files::postCreate' => NodeCreatedEvent::class, + '\OCP\Files::postUpdate' => NodeUpdatedEvent::class, + '\OCP\Files::postRename' => NodeRenamedEvent::class, + '\OCP\Files::postDelete' => NodeDeletedEvent::class, + '\OCP\Files::postTouch' => NodeTouchedEvent::class, + '\OCP\Files::postCopy' => NodeCopiedEvent::class, + 'OCP\SystemTag\ISystemTagObjectMapper::assignTags' => SingleTagAssignedEvent::class, + }; + }, $events); + + if ($newEvents !== $events) { + $newMapping[$row['id']] = json_encode($newEvents); + } + } + $result->closeCursor(); + + try { + if ($newMapping !== []) { + $this->connection->beginTransaction(); + } + foreach ($newMapping as $id => $events) { + $update = $this->connection->getQueryBuilder(); + $update->update('flow_operations') + ->set('events', $update->createNamedParameter($events)) + ->where($qb->expr()->eq('id', $update->createNamedParameter($id))) + ->executeStatement(); + } + if ($newMapping !== []) { + $this->connection->commit(); + } + } catch (\Exception $e) { + $this->connection->rollback(); + throw $e; + } + + if ($row !== false) { + $lastId = $row['id']; + } else { + break; + } + } + + return $schemaClosure(); + } +} diff --git a/apps/workflowengine/tests/ManagerTest.php b/apps/workflowengine/tests/ManagerTest.php index e5b32cd78f403..f4dba38429dba 100644 --- a/apps/workflowengine/tests/ManagerTest.php +++ b/apps/workflowengine/tests/ManagerTest.php @@ -15,6 +15,7 @@ use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Events\Node\NodeCreatedEvent; +use OCP\Files\Events\Node\NodeDeletedEvent; use OCP\Files\IRootFolder; use OCP\Files\Mount\IMountManager; use OCP\ICache; @@ -441,12 +442,12 @@ public function testUpdateOperation(): void { $check2 = ['class' => ICheck::class, 'operator' => 'eq', 'value' => 23456]; /** @noinspection PhpUnhandledExceptionInspection */ - $op = $this->manager->updateOperation($opId1, 'Test01a', [$check1, $check2], 'foohur', $adminScope, $entity, ['\OCP\Files::postDelete']); + $op = $this->manager->updateOperation($opId1, 'Test01a', [$check1, $check2], 'foohur', $adminScope, $entity, [NodeDeletedEvent::class]); $this->assertSame('Test01a', $op['name']); $this->assertSame('foohur', $op['operation']); /** @noinspection PhpUnhandledExceptionInspection */ - $op = $this->manager->updateOperation($opId2, 'Test02a', [$check1], 'barfoo', $userScope, $entity, ['\OCP\Files::postDelete']); + $op = $this->manager->updateOperation($opId2, 'Test02a', [$check1], 'barfoo', $userScope, $entity, [NodeDeletedEvent::class]); $this->assertSame('Test02a', $op['name']); $this->assertSame('barfoo', $op['operation']); diff --git a/apps/workflowengine/tests/Migration/Version3400Date20260227000000Test.php b/apps/workflowengine/tests/Migration/Version3400Date20260227000000Test.php new file mode 100644 index 0000000000000..5877d269ece32 --- /dev/null +++ b/apps/workflowengine/tests/Migration/Version3400Date20260227000000Test.php @@ -0,0 +1,82 @@ +getQueryBuilder(); + $qb->delete('flow_operations') + ->executeStatement(); + } + + public function testMigration(): void { + $db = Server::get(IDBConnection::class); + foreach (['\OCP\Files::postCreate', '\OCP\Files::postUpdate', '\OCP\Files::postRename', '\OCP\Files::postDelete', + '\OCP\Files::postTouch', '\OCP\Files::postCopy', 'OCP\SystemTag\ISystemTagObjectMapper::assignTags'] as $legacyEventName) { + $qb = $db->getQueryBuilder(); + $qb->insert('flow_operations') + ->values([ + 'class' => $qb->createNamedParameter('OCA\FlowNotifications\Flow\Operation'), + 'name' => $qb->createNamedParameter(''), + 'operation' => $qb->createNamedParameter('{"inscription":"Test"}'), + 'entity' => $qb->createNamedParameter(File::class), + 'events' => $qb->createNamedParameter(json_encode([$legacyEventName])), + ])->executeStatement(); + } + + $migration = Server::get(Version3400Date20260227000000::class); + $migration->postSchemaChange( + $this->createMock(IOutput::class), + fn () => $this->createMock(ISchemaWrapper::class), + [] + ); + + $qb = $db->getQueryBuilder(); + $events = $qb->select('events') + ->from('flow_operations') + ->executeQuery() + ->fetchFirstColumn(); + foreach ($events as $eventGroup) { + $events = json_decode($eventGroup, true); + foreach ($events as $event) { + $this->assertTrue(in_array($event, [ + NodeCreatedEvent::class, + NodeUpdatedEvent::class, + NodeDeletedEvent::class, + NodeRenamedEvent::class, + NodeTouchedEvent::class, + NodeCopiedEvent::class, + SingleTagAssignedEvent::class, + ])); + } + } + } +} diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index 9936d3543d934..3304b937f9527 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -2739,17 +2739,6 @@ - - - - - - - - - - - diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 5b6de5ff356d1..f0dcaac1f8a67 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -463,6 +463,7 @@ 'OCP\\Files\\Events\\Node\\BeforeNodeReadEvent' => $baseDir . '/lib/public/Files/Events/Node/BeforeNodeReadEvent.php', 'OCP\\Files\\Events\\Node\\BeforeNodeRenamedEvent' => $baseDir . '/lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php', 'OCP\\Files\\Events\\Node\\BeforeNodeTouchedEvent' => $baseDir . '/lib/public/Files/Events/Node/BeforeNodeTouchedEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeUpdatedEvent' => $baseDir . '/lib/public/Files/Events/Node/BeforeNodeUpdatedEvent.php', 'OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent' => $baseDir . '/lib/public/Files/Events/Node/BeforeNodeWrittenEvent.php', 'OCP\\Files\\Events\\Node\\FilesystemTornDownEvent' => $baseDir . '/lib/public/Files/Events/Node/FilesystemTornDownEvent.php', 'OCP\\Files\\Events\\Node\\NodeCopiedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeCopiedEvent.php', @@ -470,6 +471,7 @@ 'OCP\\Files\\Events\\Node\\NodeDeletedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeDeletedEvent.php', 'OCP\\Files\\Events\\Node\\NodeRenamedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeRenamedEvent.php', 'OCP\\Files\\Events\\Node\\NodeTouchedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeTouchedEvent.php', + 'OCP\\Files\\Events\\Node\\NodeUpdatedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeUpdatedEvent.php', 'OCP\\Files\\Events\\Node\\NodeWrittenEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeWrittenEvent.php', 'OCP\\Files\\File' => $baseDir . '/lib/public/Files/File.php', 'OCP\\Files\\FileInfo' => $baseDir . '/lib/public/Files/FileInfo.php', @@ -2202,6 +2204,7 @@ 'OC\\Support\\Subscription\\Assertion' => $baseDir . '/lib/private/Support/Subscription/Assertion.php', 'OC\\Support\\Subscription\\Registry' => $baseDir . '/lib/private/Support/Subscription/Registry.php', 'OC\\SystemConfig' => $baseDir . '/lib/private/SystemConfig.php', + 'OC\\SystemTag\\Events\\SingleTagAssignedEvent' => $baseDir . '/lib/private/SystemTag/Events/SingleTagAssignedEvent.php', 'OC\\SystemTag\\ManagerFactory' => $baseDir . '/lib/private/SystemTag/ManagerFactory.php', 'OC\\SystemTag\\SystemTag' => $baseDir . '/lib/private/SystemTag/SystemTag.php', 'OC\\SystemTag\\SystemTagManager' => $baseDir . '/lib/private/SystemTag/SystemTagManager.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index fbee07dafc6b4..a4e3cb86aca88 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -504,6 +504,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Files\\Events\\Node\\BeforeNodeReadEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/BeforeNodeReadEvent.php', 'OCP\\Files\\Events\\Node\\BeforeNodeRenamedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php', 'OCP\\Files\\Events\\Node\\BeforeNodeTouchedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/BeforeNodeTouchedEvent.php', + 'OCP\\Files\\Events\\Node\\BeforeNodeUpdatedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/BeforeNodeUpdatedEvent.php', 'OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/BeforeNodeWrittenEvent.php', 'OCP\\Files\\Events\\Node\\FilesystemTornDownEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/FilesystemTornDownEvent.php', 'OCP\\Files\\Events\\Node\\NodeCopiedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeCopiedEvent.php', @@ -511,6 +512,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Files\\Events\\Node\\NodeDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeDeletedEvent.php', 'OCP\\Files\\Events\\Node\\NodeRenamedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeRenamedEvent.php', 'OCP\\Files\\Events\\Node\\NodeTouchedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeTouchedEvent.php', + 'OCP\\Files\\Events\\Node\\NodeUpdatedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeUpdatedEvent.php', 'OCP\\Files\\Events\\Node\\NodeWrittenEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeWrittenEvent.php', 'OCP\\Files\\File' => __DIR__ . '/../../..' . '/lib/public/Files/File.php', 'OCP\\Files\\FileInfo' => __DIR__ . '/../../..' . '/lib/public/Files/FileInfo.php', @@ -2243,6 +2245,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Support\\Subscription\\Assertion' => __DIR__ . '/../../..' . '/lib/private/Support/Subscription/Assertion.php', 'OC\\Support\\Subscription\\Registry' => __DIR__ . '/../../..' . '/lib/private/Support/Subscription/Registry.php', 'OC\\SystemConfig' => __DIR__ . '/../../..' . '/lib/private/SystemConfig.php', + 'OC\\SystemTag\\Events\\SingleTagAssignedEvent' => __DIR__ . '/../../..' . '/lib/private/SystemTag/Events/SingleTagAssignedEvent.php', 'OC\\SystemTag\\ManagerFactory' => __DIR__ . '/../../..' . '/lib/private/SystemTag/ManagerFactory.php', 'OC\\SystemTag\\SystemTag' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTag.php', 'OC\\SystemTag\\SystemTagManager' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagManager.php', diff --git a/lib/composer/composer/installed.php b/lib/composer/composer/installed.php index ab3dd57b35611..1c86912b32d09 100644 --- a/lib/composer/composer/installed.php +++ b/lib/composer/composer/installed.php @@ -3,7 +3,7 @@ 'name' => '__root__', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '8c12590cf6f93ce7aa41f17817b3791e524da39e', + 'reference' => 'faf9ea9f2aa2deef96575c6d981821436013a58b', 'type' => 'library', 'install_path' => __DIR__ . '/../../../', 'aliases' => array(), @@ -13,7 +13,7 @@ '__root__' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '8c12590cf6f93ce7aa41f17817b3791e524da39e', + 'reference' => 'faf9ea9f2aa2deef96575c6d981821436013a58b', 'type' => 'library', 'install_path' => __DIR__ . '/../../../', 'aliases' => array(), diff --git a/lib/private/Files/Node/HookConnector.php b/lib/private/Files/Node/HookConnector.php index 1149951174cbf..3a551222fc877 100644 --- a/lib/private/Files/Node/HookConnector.php +++ b/lib/private/Files/Node/HookConnector.php @@ -19,12 +19,14 @@ use OCP\Files\Events\Node\BeforeNodeReadEvent; use OCP\Files\Events\Node\BeforeNodeRenamedEvent; use OCP\Files\Events\Node\BeforeNodeTouchedEvent; +use OCP\Files\Events\Node\BeforeNodeUpdatedEvent; use OCP\Files\Events\Node\BeforeNodeWrittenEvent; use OCP\Files\Events\Node\NodeCopiedEvent; use OCP\Files\Events\Node\NodeCreatedEvent; use OCP\Files\Events\Node\NodeDeletedEvent; use OCP\Files\Events\Node\NodeRenamedEvent; use OCP\Files\Events\Node\NodeTouchedEvent; +use OCP\Files\Events\Node\NodeUpdatedEvent; use OCP\Files\Events\Node\NodeWrittenEvent; use OCP\Files\FileInfo; use OCP\Files\IRootFolder; @@ -50,6 +52,9 @@ public function viewToNode() { Util::connectHook('OC_Filesystem', 'create', $this, 'create'); Util::connectHook('OC_Filesystem', 'post_create', $this, 'postCreate'); + Util::connectHook('OC_Filesystem', 'update', $this, 'update'); + Util::connectHook('OC_Filesystem', 'post_update', $this, 'postUpdate'); + Util::connectHook('OC_Filesystem', 'delete', $this, 'delete'); Util::connectHook('OC_Filesystem', 'post_delete', $this, 'postDelete'); @@ -101,6 +106,16 @@ public function postCreate($arguments) { $this->dispatcher->dispatchTyped($event); } + public function update(array $arguments): void { + $node = $this->getNodeForPath($arguments['path']); + $this->dispatcher->dispatchTyped(new BeforeNodeUpdatedEvent($node)); + } + + public function postUpdate(array $arguments): void { + $node = $this->getNodeForPath($arguments['path']); + $this->dispatcher->dispatchTyped(new NodeUpdatedEvent($node)); + } + public function delete($arguments) { $node = $this->getNodeForPath($arguments['path']); $this->deleteMetaCache[$node->getPath()] = $node->getFileInfo(); diff --git a/lib/private/SystemTag/Events/SingleTagAssignedEvent.php b/lib/private/SystemTag/Events/SingleTagAssignedEvent.php new file mode 100644 index 0000000000000..2a3746aa81238 --- /dev/null +++ b/lib/private/SystemTag/Events/SingleTagAssignedEvent.php @@ -0,0 +1,58 @@ + $tags + */ + public function __construct( + private readonly string $objectType, + private readonly string $objectId, + private readonly array $tags, + ) { + parent::__construct(); + } + + public function getObjectType(): string { + return $this->objectType; + } + + public function getObjectId(): string { + return $this->objectId; + } + + /** + * @return list + */ + public function getTags(): array { + return $this->tags; + } + + /** + * @return array{objectType: string, objectId: string, tagIds: list} + */ + public function getWebhookSerializable(): array { + return [ + 'objectType' => $this->getObjectType(), + 'objectId' => $this->getObjectId(), + 'tagIds' => $this->getTags(), + ]; + } +} diff --git a/lib/private/SystemTag/SystemTagObjectMapper.php b/lib/private/SystemTag/SystemTagObjectMapper.php index 508adb8c88c6c..2882e807e16f7 100644 --- a/lib/private/SystemTag/SystemTagObjectMapper.php +++ b/lib/private/SystemTag/SystemTagObjectMapper.php @@ -9,6 +9,7 @@ */ namespace OC\SystemTag; +use OC\SystemTag\Events\SingleTagAssignedEvent; use OCP\DB\Exception; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\EventDispatcher\IEventDispatcher; @@ -174,6 +175,7 @@ public function assignTags(string $objId, string $objectType, $tagIds): void { $tagsAssigned, )); $this->dispatcher->dispatchTyped(new TagAssignedEvent($objectType, [$objId], $tagsAssigned)); + $this->dispatcher->dispatchTyped(new SingleTagAssignedEvent($objectType, $objId, $tagsAssigned)); } #[Override] @@ -344,6 +346,7 @@ public function setObjectIdsForTag(string $tagId, string $objectType, array $obj (string)$objectId, [(int)$tagId] )); + $this->dispatcher->dispatchTyped(new SingleTagAssignedEvent($objectType, $objectId, [(int)$tagId])); } if (!empty($addedObjectIds)) { $this->dispatcher->dispatchTyped(new TagAssignedEvent($objectType, array_map(fn ($objectId) => (string)$objectId, $addedObjectIds), [(int)$tagId])); diff --git a/lib/public/Files/Events/Node/BeforeNodeUpdatedEvent.php b/lib/public/Files/Events/Node/BeforeNodeUpdatedEvent.php new file mode 100644 index 0000000000000..3259d3557f9ea --- /dev/null +++ b/lib/public/Files/Events/Node/BeforeNodeUpdatedEvent.php @@ -0,0 +1,18 @@ + $eventName * @since 18.0.0 */ - public function __construct(string $displayName, string $eventName) { - if (trim($displayName) === '') { - throw new \InvalidArgumentException('DisplayName must not be empty'); - } - if (trim($eventName) === '') { - throw new \InvalidArgumentException('EventName must not be empty'); - } - - $this->displayName = trim($displayName); - $this->eventName = trim($eventName); + public function __construct( + private readonly string $displayName, + private readonly string $eventName, + ) { } - /** - * returns a translated name to be presented in the web interface. - * - * Example: "created" (en), "kreita" (eo) - * - * @since 18.0.0 - */ + #[Override] public function getDisplayName(): string { return $this->displayName; } - /** - * returns the event name that is emitted by the EventDispatcher, e.g.: - * - * Example: "OCA\MyApp\Factory\Cats::postCreated" - * - * @since 18.0.0 - */ + #[Override] public function getEventName(): string { return $this->eventName; } diff --git a/lib/public/WorkflowEngine/IEntityEvent.php b/lib/public/WorkflowEngine/IEntityEvent.php index 66088c26b4784..f9cf15ba615d3 100644 --- a/lib/public/WorkflowEngine/IEntityEvent.php +++ b/lib/public/WorkflowEngine/IEntityEvent.php @@ -8,6 +8,8 @@ */ namespace OCP\WorkflowEngine; +use OCP\EventDispatcher\Event; + /** * Interface IEntityEvent * @@ -27,10 +29,11 @@ interface IEntityEvent { public function getDisplayName(): string; /** - * returns the event name that is emitted by the EventDispatcher, e.g.: + * Returns the event name that is emitted by the EventDispatcher, e.g.: * * Example: "OCA\MyApp\Factory\Cats::postCreated" * + * @return class-string * @since 18.0.0 */ public function getEventName(): string; diff --git a/tests/lib/SystemTag/SystemTagObjectMapperTest.php b/tests/lib/SystemTag/SystemTagObjectMapperTest.php index 5542d046d88f2..d2625ea69832d 100644 --- a/tests/lib/SystemTag/SystemTagObjectMapperTest.php +++ b/tests/lib/SystemTag/SystemTagObjectMapperTest.php @@ -8,6 +8,7 @@ namespace Test\SystemTag; +use OC\SystemTag\Events\SingleTagAssignedEvent; use OC\SystemTag\SystemTag; use OC\SystemTag\SystemTagManager; use OC\SystemTag\SystemTagObjectMapper; @@ -18,7 +19,6 @@ use OCP\SystemTag\ISystemTag; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; -use OCP\SystemTag\TagAssignedEvent; use OCP\SystemTag\TagNotFoundException; use OCP\SystemTag\TagUnassignedEvent; use Test\TestCase; @@ -234,20 +234,18 @@ public function testAssignUnassignTags(): void { $this->tagMapper->assignTags('1', 'testtype', [$this->tag1->getId()]); $this->assertNotNull($event); - $this->assertEquals(TagAssignedEvent::class, $event::class); + $this->assertEquals(SingleTagAssignedEvent::class, $event::class); $this->assertEquals('testtype', $event->getObjectType()); - $this->assertCount(1, $event->getObjectIds()); - $this->assertEquals('1', current($event->getObjectIds())); + $this->assertEquals('1', $event->getObjectId()); $this->assertCount(1, $event->getTags()); $this->assertEquals($this->tag1->getId(), current($event->getTags())); $this->tagMapper->assignTags('1', 'testtype', $this->tag3->getId()); $this->assertNotNull($event); - $this->assertEquals(TagAssignedEvent::class, $event::class); + $this->assertEquals(SingleTagAssignedEvent::class, $event::class); $this->assertEquals('testtype', $event->getObjectType()); - $this->assertCount(1, $event->getObjectIds()); - $this->assertEquals('1', current($event->getObjectIds())); + $this->assertEquals('1', $event->getObjectId()); $this->assertCount(1, $event->getTags()); $this->assertEquals($this->tag3->getId(), current($event->getTags()));