From 2ac88eae9c9d76e5309d14113fb2a5494a79b933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Wed, 12 Mar 2025 07:15:46 +0100 Subject: [PATCH 1/4] IBX-9060: Added mark as unread functionality for notifications --- .../NotificationServiceDecorator.php | 5 ++ .../BeforeMarkNotificationAsUnreadEvent.php | 28 +++++++++ .../MarkNotificationAsUnreadEvent.php | 28 +++++++++ .../Repository/NotificationService.php | 10 ++++ src/lib/Event/NotificationService.php | 20 +++++++ src/lib/Repository/NotificationService.php | 25 ++++++++ .../SiteAccessAware/NotificationService.php | 10 ++++ .../Repository/NotificationServiceTest.php | 19 ++++++ tests/lib/Event/NotificationServiceTest.php | 59 +++++++++++++++++++ .../NotificationServiceDecoratorTest.php | 12 ++++ 10 files changed, 216 insertions(+) create mode 100644 src/contracts/Repository/Events/Notification/BeforeMarkNotificationAsUnreadEvent.php create mode 100644 src/contracts/Repository/Events/Notification/MarkNotificationAsUnreadEvent.php diff --git a/src/contracts/Repository/Decorator/NotificationServiceDecorator.php b/src/contracts/Repository/Decorator/NotificationServiceDecorator.php index 34bfac43a0..7236473ffd 100644 --- a/src/contracts/Repository/Decorator/NotificationServiceDecorator.php +++ b/src/contracts/Repository/Decorator/NotificationServiceDecorator.php @@ -40,6 +40,11 @@ public function markNotificationAsRead(Notification $notification): void $this->innerService->markNotificationAsRead($notification); } + public function markNotificationAsUnread(Notification $notification): void + { + $this->innerService->markNotificationAsUnread($notification); + } + public function getPendingNotificationCount(): int { return $this->innerService->getPendingNotificationCount(); diff --git a/src/contracts/Repository/Events/Notification/BeforeMarkNotificationAsUnreadEvent.php b/src/contracts/Repository/Events/Notification/BeforeMarkNotificationAsUnreadEvent.php new file mode 100644 index 0000000000..38436fba6b --- /dev/null +++ b/src/contracts/Repository/Events/Notification/BeforeMarkNotificationAsUnreadEvent.php @@ -0,0 +1,28 @@ +notification = $notification; + } + + public function getNotification(): Notification + { + return $this->notification; + } +} diff --git a/src/contracts/Repository/Events/Notification/MarkNotificationAsUnreadEvent.php b/src/contracts/Repository/Events/Notification/MarkNotificationAsUnreadEvent.php new file mode 100644 index 0000000000..0b5841265d --- /dev/null +++ b/src/contracts/Repository/Events/Notification/MarkNotificationAsUnreadEvent.php @@ -0,0 +1,28 @@ +notification = $notification; + } + + public function getNotification(): Notification + { + return $this->notification; + } +} diff --git a/src/contracts/Repository/NotificationService.php b/src/contracts/Repository/NotificationService.php index 0079f5e0fc..f41b9915e1 100644 --- a/src/contracts/Repository/NotificationService.php +++ b/src/contracts/Repository/NotificationService.php @@ -49,6 +49,16 @@ public function getNotification(int $notificationId): Notification; */ public function markNotificationAsRead(Notification $notification): void; + /** + * Mark notification as unread so it no longer bother the user. + * + * @param \Ibexa\Contracts\Core\Repository\Values\Notification\Notification $notification + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException + */ + public function markNotificationAsUnread(Notification $notification): void; + /** * Get count of unread users notifications. * diff --git a/src/lib/Event/NotificationService.php b/src/lib/Event/NotificationService.php index 26e8da9fb3..13b3a5982f 100644 --- a/src/lib/Event/NotificationService.php +++ b/src/lib/Event/NotificationService.php @@ -12,9 +12,11 @@ use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeCreateNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeDeleteNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsReadEvent; +use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsUnreadEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\CreateNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\DeleteNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsReadEvent; +use Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsUnreadEvent; use Ibexa\Contracts\Core\Repository\NotificationService as NotificationServiceInterface; use Ibexa\Contracts\Core\Repository\Values\Notification\CreateStruct; use Ibexa\Contracts\Core\Repository\Values\Notification\Notification; @@ -52,6 +54,24 @@ public function markNotificationAsRead(Notification $notification): void ); } + public function markNotificationAsUnread(Notification $notification): void + { + $eventData = [$notification]; + + $beforeEvent = new BeforeMarkNotificationAsUnreadEvent(...$eventData); + + $this->eventDispatcher->dispatch($beforeEvent); + if ($beforeEvent->isPropagationStopped()) { + return; + } + + $this->innerService->markNotificationAsUnread($notification); + + $this->eventDispatcher->dispatch( + new MarkNotificationAsUnreadEvent(...$eventData) + ); + } + public function createNotification(CreateStruct $createStruct): Notification { $eventData = [$createStruct]; diff --git a/src/lib/Repository/NotificationService.php b/src/lib/Repository/NotificationService.php index 6fc56e1d1b..f107b02315 100644 --- a/src/lib/Repository/NotificationService.php +++ b/src/lib/Repository/NotificationService.php @@ -125,6 +125,31 @@ public function markNotificationAsRead(APINotification $notification): void $this->persistenceHandler->updateNotification($notification, $updateStruct); } + /** + * {@inheritdoc} + */ + public function markNotificationAsUnread(APINotification $notification): void + { + $currentUserId = $this->getCurrentUserId(); + + if (!$notification->id) { + throw new NotFoundException('Notification', $notification->id); + } + + if ($notification->ownerId !== $currentUserId) { + throw new UnauthorizedException($notification->id, 'Notification'); + } + + if ($notification->isPending) { + return; + } + + $updateStruct = new UpdateStruct(); + $updateStruct->isPending = true; + + $this->persistenceHandler->updateNotification($notification, $updateStruct); + } + /** * {@inheritdoc} */ diff --git a/src/lib/Repository/SiteAccessAware/NotificationService.php b/src/lib/Repository/SiteAccessAware/NotificationService.php index 203e8d7c9a..6d78513ff1 100644 --- a/src/lib/Repository/SiteAccessAware/NotificationService.php +++ b/src/lib/Repository/SiteAccessAware/NotificationService.php @@ -62,6 +62,16 @@ public function markNotificationAsRead(Notification $notification): void $this->service->markNotificationAsRead($notification); } + /** + * Mark notification as unread so it no longer bother the user. + * + * @param \Ibexa\Contracts\Core\Repository\Values\Notification\Notification $notification + */ + public function markNotificationAsUnread(Notification $notification): void + { + $this->service->markNotificationAsUnread($notification); + } + /** * Get count of unread users notifications. * diff --git a/tests/integration/Core/Repository/NotificationServiceTest.php b/tests/integration/Core/Repository/NotificationServiceTest.php index 7b13a4c6ff..b1a5df107a 100644 --- a/tests/integration/Core/Repository/NotificationServiceTest.php +++ b/tests/integration/Core/Repository/NotificationServiceTest.php @@ -77,6 +77,25 @@ public function testMarkNotificationAsRead() $this->assertFalse($notification->isPending); } + /** + * @covers \Ibexa\Contracts\Core\Repository\NotificationService::markNotificationAsUnread() + */ + public function testMarkNotificationAsUnread() + { + $repository = $this->getRepository(); + + $notificationId = $this->generateId('notification', 5); + /* BEGIN: Use Case */ + $notificationService = $repository->getNotificationService(); + + $notification = $notificationService->getNotification($notificationId); + $notificationService->markNotificationAsUnread($notification); + $notification = $notificationService->getNotification($notificationId); + /* END: Use Case */ + + self::assertTrue($notification->isPending); + } + /** * @covers \Ibexa\Contracts\Core\Repository\NotificationService::getPendingNotificationCount() */ diff --git a/tests/lib/Event/NotificationServiceTest.php b/tests/lib/Event/NotificationServiceTest.php index eb032d16a6..d279acbe0f 100644 --- a/tests/lib/Event/NotificationServiceTest.php +++ b/tests/lib/Event/NotificationServiceTest.php @@ -9,9 +9,11 @@ use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeCreateNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeDeleteNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsReadEvent; +use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsUnreadEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\CreateNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\DeleteNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsReadEvent; +use Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsUnreadEvent; use Ibexa\Contracts\Core\Repository\NotificationService as NotificationServiceInterface; use Ibexa\Contracts\Core\Repository\Values\Notification\CreateStruct; use Ibexa\Contracts\Core\Repository\Values\Notification\Notification; @@ -200,6 +202,31 @@ public function testMarkNotificationAsReadEvents() $this->assertSame([], $traceableEventDispatcher->getNotCalledListeners()); } + public function testMarkNotificationAsUnreadEvents() + { + $traceableEventDispatcher = $this->getEventDispatcher( + BeforeMarkNotificationAsUnreadEvent::class, + MarkNotificationAsUnreadEvent::class + ); + + $parameters = [ + $this->createMock(Notification::class), + ]; + + $innerServiceMock = $this->createMock(NotificationServiceInterface::class); + + $service = new NotificationService($innerServiceMock, $traceableEventDispatcher); + $service->markNotificationAsUnread(...$parameters); + + $calledListeners = $this->getListenersStack($traceableEventDispatcher->getCalledListeners()); + + self::assertSame($calledListeners, [ + [BeforeMarkNotificationAsUnreadEvent::class, 0], + [MarkNotificationAsUnreadEvent::class, 0], + ]); + self::assertSame([], $traceableEventDispatcher->getNotCalledListeners()); + } + public function testMarkNotificationAsReadStopPropagationInBeforeEvents() { $traceableEventDispatcher = $this->getEventDispatcher( @@ -231,6 +258,38 @@ public function testMarkNotificationAsReadStopPropagationInBeforeEvents() [MarkNotificationAsReadEvent::class, 0], ]); } + + public function testMarkNotificationAsUnreadStopPropagationInBeforeEvents() + { + $traceableEventDispatcher = $this->getEventDispatcher( + BeforeMarkNotificationAsUnreadEvent::class, + MarkNotificationAsUnreadEvent::class + ); + + $parameters = [ + $this->createMock(Notification::class), + ]; + + $innerServiceMock = $this->createMock(NotificationServiceInterface::class); + + $traceableEventDispatcher->addListener(BeforeMarkNotificationAsUnreadEvent::class, static function (BeforeMarkNotificationAsUnreadEvent $event) { + $event->stopPropagation(); + }, 10); + + $service = new NotificationService($innerServiceMock, $traceableEventDispatcher); + $service->markNotificationAsUnread(...$parameters); + + $calledListeners = $this->getListenersStack($traceableEventDispatcher->getCalledListeners()); + $notCalledListeners = $this->getListenersStack($traceableEventDispatcher->getNotCalledListeners()); + + self::assertSame($calledListeners, [ + [BeforeMarkNotificationAsUnreadEvent::class, 10], + ]); + self::assertSame($notCalledListeners, [ + [BeforeMarkNotificationAsUnreadEvent::class, 0], + [MarkNotificationAsUnreadEvent::class, 0], + ]); + } } class_alias(NotificationServiceTest::class, 'eZ\Publish\Core\Event\Tests\NotificationServiceTest'); diff --git a/tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php b/tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php index 23271ed559..4b29826a41 100644 --- a/tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php +++ b/tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php @@ -67,6 +67,18 @@ public function testMarkNotificationAsReadDecorator() $decoratedService->markNotificationAsRead(...$parameters); } + public function testMarkNotificationAsUnreadDecorator() + { + $serviceMock = $this->createServiceMock(); + $decoratedService = $this->createDecorator($serviceMock); + + $parameters = [$this->createMock(Notification::class)]; + + $serviceMock->expects(self::once())->method('markNotificationAsunread')->with(...$parameters); + + $decoratedService->markNotificationAsunread(...$parameters); + } + public function testGetPendingNotificationCountDecorator() { $serviceMock = $this->createServiceMock(); From a6178a42eb14d350aa7f5cf2e4729ab9f01589b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Wed, 12 Mar 2025 07:15:46 +0100 Subject: [PATCH 2/4] IBX-9060: Added mark as unread functionality for notifications --- .../NotificationServiceDecorator.php | 5 ++ .../BeforeMarkNotificationAsUnreadEvent.php | 28 +++++++++ .../MarkNotificationAsUnreadEvent.php | 28 +++++++++ .../Repository/NotificationService.php | 10 ++++ src/lib/Event/NotificationService.php | 20 +++++++ src/lib/Repository/NotificationService.php | 25 ++++++++ .../SiteAccessAware/NotificationService.php | 10 ++++ .../Repository/NotificationServiceTest.php | 19 ++++++ tests/lib/Event/NotificationServiceTest.php | 59 +++++++++++++++++++ .../NotificationServiceDecoratorTest.php | 12 ++++ 10 files changed, 216 insertions(+) create mode 100644 src/contracts/Repository/Events/Notification/BeforeMarkNotificationAsUnreadEvent.php create mode 100644 src/contracts/Repository/Events/Notification/MarkNotificationAsUnreadEvent.php diff --git a/src/contracts/Repository/Decorator/NotificationServiceDecorator.php b/src/contracts/Repository/Decorator/NotificationServiceDecorator.php index 34bfac43a0..7236473ffd 100644 --- a/src/contracts/Repository/Decorator/NotificationServiceDecorator.php +++ b/src/contracts/Repository/Decorator/NotificationServiceDecorator.php @@ -40,6 +40,11 @@ public function markNotificationAsRead(Notification $notification): void $this->innerService->markNotificationAsRead($notification); } + public function markNotificationAsUnread(Notification $notification): void + { + $this->innerService->markNotificationAsUnread($notification); + } + public function getPendingNotificationCount(): int { return $this->innerService->getPendingNotificationCount(); diff --git a/src/contracts/Repository/Events/Notification/BeforeMarkNotificationAsUnreadEvent.php b/src/contracts/Repository/Events/Notification/BeforeMarkNotificationAsUnreadEvent.php new file mode 100644 index 0000000000..38436fba6b --- /dev/null +++ b/src/contracts/Repository/Events/Notification/BeforeMarkNotificationAsUnreadEvent.php @@ -0,0 +1,28 @@ +notification = $notification; + } + + public function getNotification(): Notification + { + return $this->notification; + } +} diff --git a/src/contracts/Repository/Events/Notification/MarkNotificationAsUnreadEvent.php b/src/contracts/Repository/Events/Notification/MarkNotificationAsUnreadEvent.php new file mode 100644 index 0000000000..0b5841265d --- /dev/null +++ b/src/contracts/Repository/Events/Notification/MarkNotificationAsUnreadEvent.php @@ -0,0 +1,28 @@ +notification = $notification; + } + + public function getNotification(): Notification + { + return $this->notification; + } +} diff --git a/src/contracts/Repository/NotificationService.php b/src/contracts/Repository/NotificationService.php index 0079f5e0fc..f41b9915e1 100644 --- a/src/contracts/Repository/NotificationService.php +++ b/src/contracts/Repository/NotificationService.php @@ -49,6 +49,16 @@ public function getNotification(int $notificationId): Notification; */ public function markNotificationAsRead(Notification $notification): void; + /** + * Mark notification as unread so it no longer bother the user. + * + * @param \Ibexa\Contracts\Core\Repository\Values\Notification\Notification $notification + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException + */ + public function markNotificationAsUnread(Notification $notification): void; + /** * Get count of unread users notifications. * diff --git a/src/lib/Event/NotificationService.php b/src/lib/Event/NotificationService.php index 26e8da9fb3..13b3a5982f 100644 --- a/src/lib/Event/NotificationService.php +++ b/src/lib/Event/NotificationService.php @@ -12,9 +12,11 @@ use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeCreateNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeDeleteNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsReadEvent; +use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsUnreadEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\CreateNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\DeleteNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsReadEvent; +use Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsUnreadEvent; use Ibexa\Contracts\Core\Repository\NotificationService as NotificationServiceInterface; use Ibexa\Contracts\Core\Repository\Values\Notification\CreateStruct; use Ibexa\Contracts\Core\Repository\Values\Notification\Notification; @@ -52,6 +54,24 @@ public function markNotificationAsRead(Notification $notification): void ); } + public function markNotificationAsUnread(Notification $notification): void + { + $eventData = [$notification]; + + $beforeEvent = new BeforeMarkNotificationAsUnreadEvent(...$eventData); + + $this->eventDispatcher->dispatch($beforeEvent); + if ($beforeEvent->isPropagationStopped()) { + return; + } + + $this->innerService->markNotificationAsUnread($notification); + + $this->eventDispatcher->dispatch( + new MarkNotificationAsUnreadEvent(...$eventData) + ); + } + public function createNotification(CreateStruct $createStruct): Notification { $eventData = [$createStruct]; diff --git a/src/lib/Repository/NotificationService.php b/src/lib/Repository/NotificationService.php index 6fc56e1d1b..f107b02315 100644 --- a/src/lib/Repository/NotificationService.php +++ b/src/lib/Repository/NotificationService.php @@ -125,6 +125,31 @@ public function markNotificationAsRead(APINotification $notification): void $this->persistenceHandler->updateNotification($notification, $updateStruct); } + /** + * {@inheritdoc} + */ + public function markNotificationAsUnread(APINotification $notification): void + { + $currentUserId = $this->getCurrentUserId(); + + if (!$notification->id) { + throw new NotFoundException('Notification', $notification->id); + } + + if ($notification->ownerId !== $currentUserId) { + throw new UnauthorizedException($notification->id, 'Notification'); + } + + if ($notification->isPending) { + return; + } + + $updateStruct = new UpdateStruct(); + $updateStruct->isPending = true; + + $this->persistenceHandler->updateNotification($notification, $updateStruct); + } + /** * {@inheritdoc} */ diff --git a/src/lib/Repository/SiteAccessAware/NotificationService.php b/src/lib/Repository/SiteAccessAware/NotificationService.php index 203e8d7c9a..6d78513ff1 100644 --- a/src/lib/Repository/SiteAccessAware/NotificationService.php +++ b/src/lib/Repository/SiteAccessAware/NotificationService.php @@ -62,6 +62,16 @@ public function markNotificationAsRead(Notification $notification): void $this->service->markNotificationAsRead($notification); } + /** + * Mark notification as unread so it no longer bother the user. + * + * @param \Ibexa\Contracts\Core\Repository\Values\Notification\Notification $notification + */ + public function markNotificationAsUnread(Notification $notification): void + { + $this->service->markNotificationAsUnread($notification); + } + /** * Get count of unread users notifications. * diff --git a/tests/integration/Core/Repository/NotificationServiceTest.php b/tests/integration/Core/Repository/NotificationServiceTest.php index 7b13a4c6ff..b1a5df107a 100644 --- a/tests/integration/Core/Repository/NotificationServiceTest.php +++ b/tests/integration/Core/Repository/NotificationServiceTest.php @@ -77,6 +77,25 @@ public function testMarkNotificationAsRead() $this->assertFalse($notification->isPending); } + /** + * @covers \Ibexa\Contracts\Core\Repository\NotificationService::markNotificationAsUnread() + */ + public function testMarkNotificationAsUnread() + { + $repository = $this->getRepository(); + + $notificationId = $this->generateId('notification', 5); + /* BEGIN: Use Case */ + $notificationService = $repository->getNotificationService(); + + $notification = $notificationService->getNotification($notificationId); + $notificationService->markNotificationAsUnread($notification); + $notification = $notificationService->getNotification($notificationId); + /* END: Use Case */ + + self::assertTrue($notification->isPending); + } + /** * @covers \Ibexa\Contracts\Core\Repository\NotificationService::getPendingNotificationCount() */ diff --git a/tests/lib/Event/NotificationServiceTest.php b/tests/lib/Event/NotificationServiceTest.php index eb032d16a6..d279acbe0f 100644 --- a/tests/lib/Event/NotificationServiceTest.php +++ b/tests/lib/Event/NotificationServiceTest.php @@ -9,9 +9,11 @@ use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeCreateNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeDeleteNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsReadEvent; +use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsUnreadEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\CreateNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\DeleteNotificationEvent; use Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsReadEvent; +use Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsUnreadEvent; use Ibexa\Contracts\Core\Repository\NotificationService as NotificationServiceInterface; use Ibexa\Contracts\Core\Repository\Values\Notification\CreateStruct; use Ibexa\Contracts\Core\Repository\Values\Notification\Notification; @@ -200,6 +202,31 @@ public function testMarkNotificationAsReadEvents() $this->assertSame([], $traceableEventDispatcher->getNotCalledListeners()); } + public function testMarkNotificationAsUnreadEvents() + { + $traceableEventDispatcher = $this->getEventDispatcher( + BeforeMarkNotificationAsUnreadEvent::class, + MarkNotificationAsUnreadEvent::class + ); + + $parameters = [ + $this->createMock(Notification::class), + ]; + + $innerServiceMock = $this->createMock(NotificationServiceInterface::class); + + $service = new NotificationService($innerServiceMock, $traceableEventDispatcher); + $service->markNotificationAsUnread(...$parameters); + + $calledListeners = $this->getListenersStack($traceableEventDispatcher->getCalledListeners()); + + self::assertSame($calledListeners, [ + [BeforeMarkNotificationAsUnreadEvent::class, 0], + [MarkNotificationAsUnreadEvent::class, 0], + ]); + self::assertSame([], $traceableEventDispatcher->getNotCalledListeners()); + } + public function testMarkNotificationAsReadStopPropagationInBeforeEvents() { $traceableEventDispatcher = $this->getEventDispatcher( @@ -231,6 +258,38 @@ public function testMarkNotificationAsReadStopPropagationInBeforeEvents() [MarkNotificationAsReadEvent::class, 0], ]); } + + public function testMarkNotificationAsUnreadStopPropagationInBeforeEvents() + { + $traceableEventDispatcher = $this->getEventDispatcher( + BeforeMarkNotificationAsUnreadEvent::class, + MarkNotificationAsUnreadEvent::class + ); + + $parameters = [ + $this->createMock(Notification::class), + ]; + + $innerServiceMock = $this->createMock(NotificationServiceInterface::class); + + $traceableEventDispatcher->addListener(BeforeMarkNotificationAsUnreadEvent::class, static function (BeforeMarkNotificationAsUnreadEvent $event) { + $event->stopPropagation(); + }, 10); + + $service = new NotificationService($innerServiceMock, $traceableEventDispatcher); + $service->markNotificationAsUnread(...$parameters); + + $calledListeners = $this->getListenersStack($traceableEventDispatcher->getCalledListeners()); + $notCalledListeners = $this->getListenersStack($traceableEventDispatcher->getNotCalledListeners()); + + self::assertSame($calledListeners, [ + [BeforeMarkNotificationAsUnreadEvent::class, 10], + ]); + self::assertSame($notCalledListeners, [ + [BeforeMarkNotificationAsUnreadEvent::class, 0], + [MarkNotificationAsUnreadEvent::class, 0], + ]); + } } class_alias(NotificationServiceTest::class, 'eZ\Publish\Core\Event\Tests\NotificationServiceTest'); diff --git a/tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php b/tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php index 23271ed559..4b29826a41 100644 --- a/tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php +++ b/tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php @@ -67,6 +67,18 @@ public function testMarkNotificationAsReadDecorator() $decoratedService->markNotificationAsRead(...$parameters); } + public function testMarkNotificationAsUnreadDecorator() + { + $serviceMock = $this->createServiceMock(); + $decoratedService = $this->createDecorator($serviceMock); + + $parameters = [$this->createMock(Notification::class)]; + + $serviceMock->expects(self::once())->method('markNotificationAsunread')->with(...$parameters); + + $decoratedService->markNotificationAsunread(...$parameters); + } + public function testGetPendingNotificationCountDecorator() { $serviceMock = $this->createServiceMock(); From 4834cb95c805900bbb7a0805a00ff123732696ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Mon, 17 Mar 2025 09:35:53 +0100 Subject: [PATCH 3/4] IBX-9060: Applied review feedback and refactored code --- .../Notification/BeforeMarkNotificationAsUnreadEvent.php | 3 +-- .../Events/Notification/MarkNotificationAsUnreadEvent.php | 3 +-- src/contracts/Repository/NotificationService.php | 4 +--- src/lib/Repository/NotificationService.php | 3 --- src/lib/Repository/SiteAccessAware/NotificationService.php | 5 ----- .../integration/Core/Repository/NotificationServiceTest.php | 4 +--- tests/lib/Event/NotificationServiceTest.php | 4 ++-- .../Decorator/NotificationServiceDecoratorTest.php | 6 +++--- 8 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/contracts/Repository/Events/Notification/BeforeMarkNotificationAsUnreadEvent.php b/src/contracts/Repository/Events/Notification/BeforeMarkNotificationAsUnreadEvent.php index 38436fba6b..2f75e2bc8e 100644 --- a/src/contracts/Repository/Events/Notification/BeforeMarkNotificationAsUnreadEvent.php +++ b/src/contracts/Repository/Events/Notification/BeforeMarkNotificationAsUnreadEvent.php @@ -13,8 +13,7 @@ final class BeforeMarkNotificationAsUnreadEvent extends BeforeEvent { - /** @var \Ibexa\Contracts\Core\Repository\Values\Notification\Notification */ - private $notification; + private Notification $notification; public function __construct(Notification $notification) { diff --git a/src/contracts/Repository/Events/Notification/MarkNotificationAsUnreadEvent.php b/src/contracts/Repository/Events/Notification/MarkNotificationAsUnreadEvent.php index 0b5841265d..af6f4ac6c0 100644 --- a/src/contracts/Repository/Events/Notification/MarkNotificationAsUnreadEvent.php +++ b/src/contracts/Repository/Events/Notification/MarkNotificationAsUnreadEvent.php @@ -13,8 +13,7 @@ final class MarkNotificationAsUnreadEvent extends AfterEvent { - /** @var \Ibexa\Contracts\Core\Repository\Values\Notification\Notification */ - private $notification; + private Notification $notification; public function __construct(Notification $notification) { diff --git a/src/contracts/Repository/NotificationService.php b/src/contracts/Repository/NotificationService.php index f41b9915e1..b702c99c41 100644 --- a/src/contracts/Repository/NotificationService.php +++ b/src/contracts/Repository/NotificationService.php @@ -50,9 +50,7 @@ public function getNotification(int $notificationId): Notification; public function markNotificationAsRead(Notification $notification): void; /** - * Mark notification as unread so it no longer bother the user. - * - * @param \Ibexa\Contracts\Core\Repository\Values\Notification\Notification $notification + * Marks the given notification as unread so it is shown again as new to the user. * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException diff --git a/src/lib/Repository/NotificationService.php b/src/lib/Repository/NotificationService.php index f107b02315..4eef0f7746 100644 --- a/src/lib/Repository/NotificationService.php +++ b/src/lib/Repository/NotificationService.php @@ -125,9 +125,6 @@ public function markNotificationAsRead(APINotification $notification): void $this->persistenceHandler->updateNotification($notification, $updateStruct); } - /** - * {@inheritdoc} - */ public function markNotificationAsUnread(APINotification $notification): void { $currentUserId = $this->getCurrentUserId(); diff --git a/src/lib/Repository/SiteAccessAware/NotificationService.php b/src/lib/Repository/SiteAccessAware/NotificationService.php index 6d78513ff1..e350b1592a 100644 --- a/src/lib/Repository/SiteAccessAware/NotificationService.php +++ b/src/lib/Repository/SiteAccessAware/NotificationService.php @@ -62,11 +62,6 @@ public function markNotificationAsRead(Notification $notification): void $this->service->markNotificationAsRead($notification); } - /** - * Mark notification as unread so it no longer bother the user. - * - * @param \Ibexa\Contracts\Core\Repository\Values\Notification\Notification $notification - */ public function markNotificationAsUnread(Notification $notification): void { $this->service->markNotificationAsUnread($notification); diff --git a/tests/integration/Core/Repository/NotificationServiceTest.php b/tests/integration/Core/Repository/NotificationServiceTest.php index b1a5df107a..83555ba975 100644 --- a/tests/integration/Core/Repository/NotificationServiceTest.php +++ b/tests/integration/Core/Repository/NotificationServiceTest.php @@ -80,18 +80,16 @@ public function testMarkNotificationAsRead() /** * @covers \Ibexa\Contracts\Core\Repository\NotificationService::markNotificationAsUnread() */ - public function testMarkNotificationAsUnread() + public function testMarkNotificationAsUnread(): void { $repository = $this->getRepository(); $notificationId = $this->generateId('notification', 5); - /* BEGIN: Use Case */ $notificationService = $repository->getNotificationService(); $notification = $notificationService->getNotification($notificationId); $notificationService->markNotificationAsUnread($notification); $notification = $notificationService->getNotification($notificationId); - /* END: Use Case */ self::assertTrue($notification->isPending); } diff --git a/tests/lib/Event/NotificationServiceTest.php b/tests/lib/Event/NotificationServiceTest.php index d279acbe0f..592408698f 100644 --- a/tests/lib/Event/NotificationServiceTest.php +++ b/tests/lib/Event/NotificationServiceTest.php @@ -202,7 +202,7 @@ public function testMarkNotificationAsReadEvents() $this->assertSame([], $traceableEventDispatcher->getNotCalledListeners()); } - public function testMarkNotificationAsUnreadEvents() + public function testMarkNotificationAsUnreadEvents(): void { $traceableEventDispatcher = $this->getEventDispatcher( BeforeMarkNotificationAsUnreadEvent::class, @@ -259,7 +259,7 @@ public function testMarkNotificationAsReadStopPropagationInBeforeEvents() ]); } - public function testMarkNotificationAsUnreadStopPropagationInBeforeEvents() + public function testMarkNotificationAsUnreadStopPropagationInBeforeEvents(): void { $traceableEventDispatcher = $this->getEventDispatcher( BeforeMarkNotificationAsUnreadEvent::class, diff --git a/tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php b/tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php index 4b29826a41..8fbcffc0da 100644 --- a/tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php +++ b/tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php @@ -67,16 +67,16 @@ public function testMarkNotificationAsReadDecorator() $decoratedService->markNotificationAsRead(...$parameters); } - public function testMarkNotificationAsUnreadDecorator() + public function testMarkNotificationAsUnreadDecorator(): void { $serviceMock = $this->createServiceMock(); $decoratedService = $this->createDecorator($serviceMock); $parameters = [$this->createMock(Notification::class)]; - $serviceMock->expects(self::once())->method('markNotificationAsunread')->with(...$parameters); + $serviceMock->expects(self::once())->method('markNotificationAsUnread')->with(...$parameters); - $decoratedService->markNotificationAsunread(...$parameters); + $decoratedService->markNotificationAsUnread(...$parameters); } public function testGetPendingNotificationCountDecorator() From 500538651d09244a2565c8ad010aba97c17657c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Bia=C5=82czak?= Date: Mon, 17 Mar 2025 09:51:35 +0100 Subject: [PATCH 4/4] IBX-9060: Fixed UnauthorizedException module/function parameters in NotificationService and resolved PHPStan error --- phpstan-baseline.neon | 6 --- src/lib/Repository/NotificationService.php | 4 +- .../Repository/NotificationServiceTest.php | 5 ++ tests/lib/Event/NotificationServiceTest.php | 52 +++++++++++++------ 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 347c434561..0c5c343b4a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -24882,12 +24882,6 @@ parameters: count: 1 path: src/lib/Repository/NameSchema/NameSchemaService.php - - - message: '#^Parameter \#1 \$module of class Ibexa\\Core\\Base\\Exceptions\\UnauthorizedException constructor expects string, int\\|int\<1, max\> given\.$#' - identifier: argument.type - count: 1 - path: src/lib/Repository/NotificationService.php - - message: '#^Parameter \#2 \$whatIsWrong of class Ibexa\\Core\\Base\\Exceptions\\InvalidArgumentException constructor expects string, int given\.$#' identifier: argument.type diff --git a/src/lib/Repository/NotificationService.php b/src/lib/Repository/NotificationService.php index 4eef0f7746..35a63f91f5 100644 --- a/src/lib/Repository/NotificationService.php +++ b/src/lib/Repository/NotificationService.php @@ -112,7 +112,7 @@ public function markNotificationAsRead(APINotification $notification): void } if ($notification->ownerId !== $currentUserId) { - throw new UnauthorizedException($notification->id, 'Notification'); + throw new UnauthorizedException('notification', 'update', ['id' => $notification->id]); } if (!$notification->isPending) { @@ -134,7 +134,7 @@ public function markNotificationAsUnread(APINotification $notification): void } if ($notification->ownerId !== $currentUserId) { - throw new UnauthorizedException($notification->id, 'Notification'); + throw new UnauthorizedException('notification', 'update', ['id' => $notification->id]); } if ($notification->isPending) { diff --git a/tests/integration/Core/Repository/NotificationServiceTest.php b/tests/integration/Core/Repository/NotificationServiceTest.php index 83555ba975..0825e56fe6 100644 --- a/tests/integration/Core/Repository/NotificationServiceTest.php +++ b/tests/integration/Core/Repository/NotificationServiceTest.php @@ -88,6 +88,11 @@ public function testMarkNotificationAsUnread(): void $notificationService = $repository->getNotificationService(); $notification = $notificationService->getNotification($notificationId); + $notificationService->markNotificationAsRead($notification); + + $notification = $notificationService->getNotification($notificationId); + self::assertFalse($notification->isPending); + $notificationService->markNotificationAsUnread($notification); $notification = $notificationService->getNotification($notificationId); diff --git a/tests/lib/Event/NotificationServiceTest.php b/tests/lib/Event/NotificationServiceTest.php index 592408698f..17fc08b0e7 100644 --- a/tests/lib/Event/NotificationServiceTest.php +++ b/tests/lib/Event/NotificationServiceTest.php @@ -65,9 +65,13 @@ public function testReturnCreateNotificationResultInBeforeEvents() $innerServiceMock = $this->createMock(NotificationServiceInterface::class); $innerServiceMock->method('createNotification')->willReturn($notification); - $traceableEventDispatcher->addListener(BeforeCreateNotificationEvent::class, static function (BeforeCreateNotificationEvent $event) use ($eventNotification) { - $event->setNotification($eventNotification); - }, 10); + $traceableEventDispatcher->addListener( + BeforeCreateNotificationEvent::class, + static function (BeforeCreateNotificationEvent $event) use ($eventNotification): void { + $event->setNotification($eventNotification); + }, + 10 + ); $service = new NotificationService($innerServiceMock, $traceableEventDispatcher); $result = $service->createNotification(...$parameters); @@ -99,10 +103,14 @@ public function testCreateNotificationStopPropagationInBeforeEvents() $innerServiceMock = $this->createMock(NotificationServiceInterface::class); $innerServiceMock->method('createNotification')->willReturn($notification); - $traceableEventDispatcher->addListener(BeforeCreateNotificationEvent::class, static function (BeforeCreateNotificationEvent $event) use ($eventNotification) { - $event->setNotification($eventNotification); - $event->stopPropagation(); - }, 10); + $traceableEventDispatcher->addListener( + BeforeCreateNotificationEvent::class, + static function (BeforeCreateNotificationEvent $event) use ($eventNotification): void { + $event->setNotification($eventNotification); + $event->stopPropagation(); + }, + 10 + ); $service = new NotificationService($innerServiceMock, $traceableEventDispatcher); $result = $service->createNotification(...$parameters); @@ -158,9 +166,13 @@ public function testDeleteNotificationStopPropagationInBeforeEvents() $innerServiceMock = $this->createMock(NotificationServiceInterface::class); - $traceableEventDispatcher->addListener(BeforeDeleteNotificationEvent::class, static function (BeforeDeleteNotificationEvent $event) { - $event->stopPropagation(); - }, 10); + $traceableEventDispatcher->addListener( + BeforeDeleteNotificationEvent::class, + static function (BeforeDeleteNotificationEvent $event): void { + $event->stopPropagation(); + }, + 10 + ); $service = new NotificationService($innerServiceMock, $traceableEventDispatcher); $service->deleteNotification(...$parameters); @@ -240,9 +252,13 @@ public function testMarkNotificationAsReadStopPropagationInBeforeEvents() $innerServiceMock = $this->createMock(NotificationServiceInterface::class); - $traceableEventDispatcher->addListener(BeforeMarkNotificationAsReadEvent::class, static function (BeforeMarkNotificationAsReadEvent $event) { - $event->stopPropagation(); - }, 10); + $traceableEventDispatcher->addListener( + BeforeMarkNotificationAsReadEvent::class, + static function (BeforeMarkNotificationAsReadEvent $event): void { + $event->stopPropagation(); + }, + 10 + ); $service = new NotificationService($innerServiceMock, $traceableEventDispatcher); $service->markNotificationAsRead(...$parameters); @@ -272,9 +288,13 @@ public function testMarkNotificationAsUnreadStopPropagationInBeforeEvents(): voi $innerServiceMock = $this->createMock(NotificationServiceInterface::class); - $traceableEventDispatcher->addListener(BeforeMarkNotificationAsUnreadEvent::class, static function (BeforeMarkNotificationAsUnreadEvent $event) { - $event->stopPropagation(); - }, 10); + $traceableEventDispatcher->addListener( + BeforeMarkNotificationAsUnreadEvent::class, + static function (BeforeMarkNotificationAsUnreadEvent $event): void { + $event->stopPropagation(); + }, + 10 + ); $service = new NotificationService($innerServiceMock, $traceableEventDispatcher); $service->markNotificationAsUnread(...$parameters);