Skip to content

Commit 793ee0e

Browse files
authored
IBX-9060: Added mark as unread functionality for notifications (#510)
* IBX-9060: Added mark as unread functionality for notifications * IBX-9060: Added mark as unread functionality for notifications * IBX-9060: Applied review feedback and refactored code * IBX-9060: Fixed UnauthorizedException module/function parameters in NotificationService and resolved PHPStan error
1 parent 375c949 commit 793ee0e

11 files changed

+241
-20
lines changed

phpstan-baseline.neon

-6
Original file line numberDiff line numberDiff line change
@@ -24876,12 +24876,6 @@ parameters:
2487624876
count: 1
2487724877
path: src/lib/Repository/NameSchema/NameSchemaService.php
2487824878

24879-
-
24880-
message: '#^Parameter \#1 \$module of class Ibexa\\Core\\Base\\Exceptions\\UnauthorizedException constructor expects string, int\<min, \-1\>\|int\<1, max\> given\.$#'
24881-
identifier: argument.type
24882-
count: 1
24883-
path: src/lib/Repository/NotificationService.php
24884-
2488524879
-
2488624880
message: '#^Parameter \#2 \$whatIsWrong of class Ibexa\\Core\\Base\\Exceptions\\InvalidArgumentException constructor expects string, int given\.$#'
2488724881
identifier: argument.type

src/contracts/Repository/Decorator/NotificationServiceDecorator.php

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public function markNotificationAsRead(Notification $notification): void
4040
$this->innerService->markNotificationAsRead($notification);
4141
}
4242

43+
public function markNotificationAsUnread(Notification $notification): void
44+
{
45+
$this->innerService->markNotificationAsUnread($notification);
46+
}
47+
4348
public function getPendingNotificationCount(): int
4449
{
4550
return $this->innerService->getPendingNotificationCount();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Contracts\Core\Repository\Events\Notification;
10+
11+
use Ibexa\Contracts\Core\Repository\Event\BeforeEvent;
12+
use Ibexa\Contracts\Core\Repository\Values\Notification\Notification;
13+
14+
final class BeforeMarkNotificationAsUnreadEvent extends BeforeEvent
15+
{
16+
private Notification $notification;
17+
18+
public function __construct(Notification $notification)
19+
{
20+
$this->notification = $notification;
21+
}
22+
23+
public function getNotification(): Notification
24+
{
25+
return $this->notification;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Contracts\Core\Repository\Events\Notification;
10+
11+
use Ibexa\Contracts\Core\Repository\Event\AfterEvent;
12+
use Ibexa\Contracts\Core\Repository\Values\Notification\Notification;
13+
14+
final class MarkNotificationAsUnreadEvent extends AfterEvent
15+
{
16+
private Notification $notification;
17+
18+
public function __construct(Notification $notification)
19+
{
20+
$this->notification = $notification;
21+
}
22+
23+
public function getNotification(): Notification
24+
{
25+
return $this->notification;
26+
}
27+
}

src/contracts/Repository/NotificationService.php

+8
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ public function getNotification(int $notificationId): Notification;
4949
*/
5050
public function markNotificationAsRead(Notification $notification): void;
5151

52+
/**
53+
* Marks the given notification as unread so it is shown again as new to the user.
54+
*
55+
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException
56+
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException
57+
*/
58+
public function markNotificationAsUnread(Notification $notification): void;
59+
5260
/**
5361
* Get count of unread users notifications.
5462
*

src/lib/Event/NotificationService.php

+20
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeCreateNotificationEvent;
1313
use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeDeleteNotificationEvent;
1414
use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsReadEvent;
15+
use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsUnreadEvent;
1516
use Ibexa\Contracts\Core\Repository\Events\Notification\CreateNotificationEvent;
1617
use Ibexa\Contracts\Core\Repository\Events\Notification\DeleteNotificationEvent;
1718
use Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsReadEvent;
19+
use Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsUnreadEvent;
1820
use Ibexa\Contracts\Core\Repository\NotificationService as NotificationServiceInterface;
1921
use Ibexa\Contracts\Core\Repository\Values\Notification\CreateStruct;
2022
use Ibexa\Contracts\Core\Repository\Values\Notification\Notification;
@@ -52,6 +54,24 @@ public function markNotificationAsRead(Notification $notification): void
5254
);
5355
}
5456

57+
public function markNotificationAsUnread(Notification $notification): void
58+
{
59+
$eventData = [$notification];
60+
61+
$beforeEvent = new BeforeMarkNotificationAsUnreadEvent(...$eventData);
62+
63+
$this->eventDispatcher->dispatch($beforeEvent);
64+
if ($beforeEvent->isPropagationStopped()) {
65+
return;
66+
}
67+
68+
$this->innerService->markNotificationAsUnread($notification);
69+
70+
$this->eventDispatcher->dispatch(
71+
new MarkNotificationAsUnreadEvent(...$eventData)
72+
);
73+
}
74+
5575
public function createNotification(CreateStruct $createStruct): Notification
5676
{
5777
$eventData = [$createStruct];

src/lib/Repository/NotificationService.php

+23-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public function markNotificationAsRead(APINotification $notification): void
112112
}
113113

114114
if ($notification->ownerId !== $currentUserId) {
115-
throw new UnauthorizedException($notification->id, 'Notification');
115+
throw new UnauthorizedException('notification', 'update', ['id' => $notification->id]);
116116
}
117117

118118
if (!$notification->isPending) {
@@ -125,6 +125,28 @@ public function markNotificationAsRead(APINotification $notification): void
125125
$this->persistenceHandler->updateNotification($notification, $updateStruct);
126126
}
127127

128+
public function markNotificationAsUnread(APINotification $notification): void
129+
{
130+
$currentUserId = $this->getCurrentUserId();
131+
132+
if (!$notification->id) {
133+
throw new NotFoundException('Notification', $notification->id);
134+
}
135+
136+
if ($notification->ownerId !== $currentUserId) {
137+
throw new UnauthorizedException('notification', 'update', ['id' => $notification->id]);
138+
}
139+
140+
if ($notification->isPending) {
141+
return;
142+
}
143+
144+
$updateStruct = new UpdateStruct();
145+
$updateStruct->isPending = true;
146+
147+
$this->persistenceHandler->updateNotification($notification, $updateStruct);
148+
}
149+
128150
/**
129151
* {@inheritdoc}
130152
*/

src/lib/Repository/SiteAccessAware/NotificationService.php

+5
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ public function markNotificationAsRead(Notification $notification): void
6262
$this->service->markNotificationAsRead($notification);
6363
}
6464

65+
public function markNotificationAsUnread(Notification $notification): void
66+
{
67+
$this->service->markNotificationAsUnread($notification);
68+
}
69+
6570
/**
6671
* Get count of unread users notifications.
6772
*

tests/integration/Core/Repository/NotificationServiceTest.php

+22
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,28 @@ public function testMarkNotificationAsRead()
7777
$this->assertFalse($notification->isPending);
7878
}
7979

80+
/**
81+
* @covers \Ibexa\Contracts\Core\Repository\NotificationService::markNotificationAsUnread()
82+
*/
83+
public function testMarkNotificationAsUnread(): void
84+
{
85+
$repository = $this->getRepository();
86+
87+
$notificationId = $this->generateId('notification', 5);
88+
$notificationService = $repository->getNotificationService();
89+
90+
$notification = $notificationService->getNotification($notificationId);
91+
$notificationService->markNotificationAsRead($notification);
92+
93+
$notification = $notificationService->getNotification($notificationId);
94+
self::assertFalse($notification->isPending);
95+
96+
$notificationService->markNotificationAsUnread($notification);
97+
$notification = $notificationService->getNotification($notificationId);
98+
99+
self::assertTrue($notification->isPending);
100+
}
101+
80102
/**
81103
* @covers \Ibexa\Contracts\Core\Repository\NotificationService::getPendingNotificationCount()
82104
*/

tests/lib/Event/NotificationServiceTest.php

+92-13
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeCreateNotificationEvent;
1010
use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeDeleteNotificationEvent;
1111
use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsReadEvent;
12+
use Ibexa\Contracts\Core\Repository\Events\Notification\BeforeMarkNotificationAsUnreadEvent;
1213
use Ibexa\Contracts\Core\Repository\Events\Notification\CreateNotificationEvent;
1314
use Ibexa\Contracts\Core\Repository\Events\Notification\DeleteNotificationEvent;
1415
use Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsReadEvent;
16+
use Ibexa\Contracts\Core\Repository\Events\Notification\MarkNotificationAsUnreadEvent;
1517
use Ibexa\Contracts\Core\Repository\NotificationService as NotificationServiceInterface;
1618
use Ibexa\Contracts\Core\Repository\Values\Notification\CreateStruct;
1719
use Ibexa\Contracts\Core\Repository\Values\Notification\Notification;
@@ -63,9 +65,13 @@ public function testReturnCreateNotificationResultInBeforeEvents()
6365
$innerServiceMock = $this->createMock(NotificationServiceInterface::class);
6466
$innerServiceMock->method('createNotification')->willReturn($notification);
6567

66-
$traceableEventDispatcher->addListener(BeforeCreateNotificationEvent::class, static function (BeforeCreateNotificationEvent $event) use ($eventNotification) {
67-
$event->setNotification($eventNotification);
68-
}, 10);
68+
$traceableEventDispatcher->addListener(
69+
BeforeCreateNotificationEvent::class,
70+
static function (BeforeCreateNotificationEvent $event) use ($eventNotification): void {
71+
$event->setNotification($eventNotification);
72+
},
73+
10
74+
);
6975

7076
$service = new NotificationService($innerServiceMock, $traceableEventDispatcher);
7177
$result = $service->createNotification(...$parameters);
@@ -97,10 +103,14 @@ public function testCreateNotificationStopPropagationInBeforeEvents()
97103
$innerServiceMock = $this->createMock(NotificationServiceInterface::class);
98104
$innerServiceMock->method('createNotification')->willReturn($notification);
99105

100-
$traceableEventDispatcher->addListener(BeforeCreateNotificationEvent::class, static function (BeforeCreateNotificationEvent $event) use ($eventNotification) {
101-
$event->setNotification($eventNotification);
102-
$event->stopPropagation();
103-
}, 10);
106+
$traceableEventDispatcher->addListener(
107+
BeforeCreateNotificationEvent::class,
108+
static function (BeforeCreateNotificationEvent $event) use ($eventNotification): void {
109+
$event->setNotification($eventNotification);
110+
$event->stopPropagation();
111+
},
112+
10
113+
);
104114

105115
$service = new NotificationService($innerServiceMock, $traceableEventDispatcher);
106116
$result = $service->createNotification(...$parameters);
@@ -156,9 +166,13 @@ public function testDeleteNotificationStopPropagationInBeforeEvents()
156166

157167
$innerServiceMock = $this->createMock(NotificationServiceInterface::class);
158168

159-
$traceableEventDispatcher->addListener(BeforeDeleteNotificationEvent::class, static function (BeforeDeleteNotificationEvent $event) {
160-
$event->stopPropagation();
161-
}, 10);
169+
$traceableEventDispatcher->addListener(
170+
BeforeDeleteNotificationEvent::class,
171+
static function (BeforeDeleteNotificationEvent $event): void {
172+
$event->stopPropagation();
173+
},
174+
10
175+
);
162176

163177
$service = new NotificationService($innerServiceMock, $traceableEventDispatcher);
164178
$service->deleteNotification(...$parameters);
@@ -200,6 +214,31 @@ public function testMarkNotificationAsReadEvents()
200214
$this->assertSame([], $traceableEventDispatcher->getNotCalledListeners());
201215
}
202216

217+
public function testMarkNotificationAsUnreadEvents(): void
218+
{
219+
$traceableEventDispatcher = $this->getEventDispatcher(
220+
BeforeMarkNotificationAsUnreadEvent::class,
221+
MarkNotificationAsUnreadEvent::class
222+
);
223+
224+
$parameters = [
225+
$this->createMock(Notification::class),
226+
];
227+
228+
$innerServiceMock = $this->createMock(NotificationServiceInterface::class);
229+
230+
$service = new NotificationService($innerServiceMock, $traceableEventDispatcher);
231+
$service->markNotificationAsUnread(...$parameters);
232+
233+
$calledListeners = $this->getListenersStack($traceableEventDispatcher->getCalledListeners());
234+
235+
self::assertSame($calledListeners, [
236+
[BeforeMarkNotificationAsUnreadEvent::class, 0],
237+
[MarkNotificationAsUnreadEvent::class, 0],
238+
]);
239+
self::assertSame([], $traceableEventDispatcher->getNotCalledListeners());
240+
}
241+
203242
public function testMarkNotificationAsReadStopPropagationInBeforeEvents()
204243
{
205244
$traceableEventDispatcher = $this->getEventDispatcher(
@@ -213,9 +252,13 @@ public function testMarkNotificationAsReadStopPropagationInBeforeEvents()
213252

214253
$innerServiceMock = $this->createMock(NotificationServiceInterface::class);
215254

216-
$traceableEventDispatcher->addListener(BeforeMarkNotificationAsReadEvent::class, static function (BeforeMarkNotificationAsReadEvent $event) {
217-
$event->stopPropagation();
218-
}, 10);
255+
$traceableEventDispatcher->addListener(
256+
BeforeMarkNotificationAsReadEvent::class,
257+
static function (BeforeMarkNotificationAsReadEvent $event): void {
258+
$event->stopPropagation();
259+
},
260+
10
261+
);
219262

220263
$service = new NotificationService($innerServiceMock, $traceableEventDispatcher);
221264
$service->markNotificationAsRead(...$parameters);
@@ -231,6 +274,42 @@ public function testMarkNotificationAsReadStopPropagationInBeforeEvents()
231274
[MarkNotificationAsReadEvent::class, 0],
232275
]);
233276
}
277+
278+
public function testMarkNotificationAsUnreadStopPropagationInBeforeEvents(): void
279+
{
280+
$traceableEventDispatcher = $this->getEventDispatcher(
281+
BeforeMarkNotificationAsUnreadEvent::class,
282+
MarkNotificationAsUnreadEvent::class
283+
);
284+
285+
$parameters = [
286+
$this->createMock(Notification::class),
287+
];
288+
289+
$innerServiceMock = $this->createMock(NotificationServiceInterface::class);
290+
291+
$traceableEventDispatcher->addListener(
292+
BeforeMarkNotificationAsUnreadEvent::class,
293+
static function (BeforeMarkNotificationAsUnreadEvent $event): void {
294+
$event->stopPropagation();
295+
},
296+
10
297+
);
298+
299+
$service = new NotificationService($innerServiceMock, $traceableEventDispatcher);
300+
$service->markNotificationAsUnread(...$parameters);
301+
302+
$calledListeners = $this->getListenersStack($traceableEventDispatcher->getCalledListeners());
303+
$notCalledListeners = $this->getListenersStack($traceableEventDispatcher->getNotCalledListeners());
304+
305+
self::assertSame($calledListeners, [
306+
[BeforeMarkNotificationAsUnreadEvent::class, 10],
307+
]);
308+
self::assertSame($notCalledListeners, [
309+
[BeforeMarkNotificationAsUnreadEvent::class, 0],
310+
[MarkNotificationAsUnreadEvent::class, 0],
311+
]);
312+
}
234313
}
235314

236315
class_alias(NotificationServiceTest::class, 'eZ\Publish\Core\Event\Tests\NotificationServiceTest');

tests/lib/Repository/Decorator/NotificationServiceDecoratorTest.php

+12
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@ public function testMarkNotificationAsReadDecorator()
6767
$decoratedService->markNotificationAsRead(...$parameters);
6868
}
6969

70+
public function testMarkNotificationAsUnreadDecorator(): void
71+
{
72+
$serviceMock = $this->createServiceMock();
73+
$decoratedService = $this->createDecorator($serviceMock);
74+
75+
$parameters = [$this->createMock(Notification::class)];
76+
77+
$serviceMock->expects(self::once())->method('markNotificationAsUnread')->with(...$parameters);
78+
79+
$decoratedService->markNotificationAsUnread(...$parameters);
80+
}
81+
7082
public function testGetPendingNotificationCountDecorator()
7183
{
7284
$serviceMock = $this->createServiceMock();

0 commit comments

Comments
 (0)