Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.06% covered (success)
93.06%
161 / 173
40.00% covered (danger)
40.00%
4 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
NotificationListener
93.06% covered (success)
93.06%
161 / 173
40.00% covered (danger)
40.00%
4 / 10
25.21
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 transport
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 supportedEvents
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 handle
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
4
 sendSignNotification
94.87% covered (success)
94.87%
37 / 39
0.00% covered (danger)
0.00%
0 / 1
6.00
 sendSignedNotification
97.62% covered (success)
97.62%
41 / 42
0.00% covered (danger)
0.00%
0 / 1
3
 sendCanceledNotification
94.87% covered (success)
94.87%
37 / 39
0.00% covered (danger)
0.00%
0 / 1
4.00
 getFileParameter
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
2.00
 getUserParameter
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getFromSignedParameter
50.00% covered (danger)
50.00%
5 / 10
0.00% covered (danger)
0.00%
0 / 1
2.50
1<?php
2
3declare(strict_types=1);
4/**
5 * SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
6 * SPDX-License-Identifier: AGPL-3.0-or-later
7 */
8
9namespace OCA\Libresign\Listener;
10
11use OCA\Libresign\AppInfo\Application as AppInfoApplication;
12use OCA\Libresign\Db\File as FileEntity;
13use OCA\Libresign\Db\SignRequest;
14use OCA\Libresign\Events\SendSignNotificationEvent;
15use OCA\Libresign\Events\SignedEvent;
16use OCA\Libresign\Events\SignRequestCanceledEvent;
17use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod;
18use OCA\Libresign\Service\Notification\InAppChannel;
19use OCA\Libresign\Service\Notification\InAppPayload;
20use OCA\Libresign\Service\Notification\NotificationHistoryService;
21use OCA\Libresign\Service\Notification\NotificationMessage;
22use OCA\Libresign\Service\Notification\NotificationModule;
23use OCA\Libresign\Service\NotificationDeliveryService;
24use OCA\Libresign\Service\NotificationPreferenceResolver;
25use OCP\AppFramework\Utility\ITimeFactory;
26use OCP\EventDispatcher\Event;
27use OCP\IURLGenerator;
28use OCP\IUser;
29use OCP\IUserSession;
30use OCP\Notification\IManager;
31
32class NotificationListener implements NotificationModule {
33    public function __construct(
34        private IManager $notificationManager,
35        protected IUserSession $userSession,
36        private ITimeFactory $timeFactory,
37        protected IURLGenerator $url,
38        private NotificationPreferenceResolver $notificationPreferenceResolver,
39        private NotificationDeliveryService $deliveryService,
40        private InAppChannel $channel,
41        private NotificationHistoryService $history,
42    ) {
43    }
44
45    #[\Override]
46    public function transport(): \OCA\Libresign\Service\Notification\NotificationChannel {
47        return $this->channel;
48    }
49
50    /** @return list<class-string<Event>> */
51    #[\Override]
52    public static function supportedEvents(): array {
53        return [SendSignNotificationEvent::class, SignedEvent::class, SignRequestCanceledEvent::class];
54    }
55
56    #[\Override]
57    public function handle(Event $event): void {
58        if ($event instanceof SendSignNotificationEvent) {
59            $this->sendSignNotification(
60                $event->getSignRequest(),
61                $event->getLibreSignFile(),
62                $event->getIdentifyMethod(),
63                $event->getDeliveryEventId(),
64            );
65        } elseif ($event instanceof SignedEvent) {
66            $this->sendSignedNotification(
67                $event->getSignRequest(),
68                $event->getLibreSignFile(),
69                $event->getIdentifyMethod(),
70                $event->getDeliveryEventId(),
71            );
72        } elseif ($event instanceof SignRequestCanceledEvent) {
73            $this->sendCanceledNotification(
74                $event->getSignRequest(),
75                $event->getLibreSignFile(),
76                $event->getIdentifyMethod(),
77                $event->getDeliveryEventId(),
78            );
79        }
80    }
81
82    private function sendSignNotification(
83        SignRequest $signRequest,
84        FileEntity $libreSignFile,
85        IIdentifyMethod $identifyMethod,
86        string $eventId,
87    ): void {
88        $actor = $this->userSession->getUser();
89        if (!$actor instanceof IUser) {
90            return;
91        }
92        if ($identifyMethod->getEntity()->isDeletedAccount()) {
93            return;
94        }
95        if ($identifyMethod->getName() !== 'account') {
96            return;
97        }
98        $notificationDisabled = $this->notificationPreferenceResolver->isInAppNotificationDisabled(
99            $identifyMethod->getEntity()->getIdentifierValue(),
100            SendSignNotificationEvent::FILE_TO_SIGN,
101            true,
102        );
103        if ($notificationDisabled) {
104            return;
105        }
106        $notification = $this->notificationManager->createNotification();
107        $notification
108            ->setApp(AppInfoApplication::APP_ID)
109            ->setObject('signRequest', (string)$signRequest->getId())
110            ->setDateTime((new \DateTime())->setTimestamp($this->timeFactory->now()->getTimestamp()))
111            ->setUser($identifyMethod->getEntity()->getIdentifierValue());
112        $subjectParameters = [
113            'from' => $this->getUserParameter(
114                $actor->getUID(),
115                $actor->getDisplayName(),
116            ),
117            'file' => $this->getFileParameter($signRequest, $libreSignFile),
118            'signRequest' => [
119                'type' => 'sign-request',
120                'id' => (string)$signRequest->getId(),
121                'name' => $actor->getDisplayName(),
122            ],
123        ];
124        $subject = $this->history->isFirst($signRequest, 'notify') ? 'new_sign_request' : 'update_sign_request';
125        $notification->setSubject($subject, $subjectParameters);
126        $this->deliveryService->dispatch(new NotificationMessage(
127            $eventId, 'sign_request', 'nextcloud', $libreSignFile, $signRequest,
128            $identifyMethod->getEntity()->getIdentifierValue(), new InAppPayload($notification, $subjectParameters),
129            'notify',
130        ), $this->channel);
131    }
132
133    private function sendSignedNotification(
134        SignRequest $signRequest,
135        FileEntity $libreSignFile,
136        IIdentifyMethod $identifyMethod,
137        string $eventId,
138    ): void {
139
140        $actorId = $libreSignFile->getUserId();
141
142        if ($identifyMethod->getEntity()->isDeletedAccount()) {
143            return;
144        }
145        $notificationDisabled = $this->notificationPreferenceResolver->isInAppNotificationDisabled(
146            $libreSignFile->getUserId(),
147            SignedEvent::FILE_SIGNED,
148            true,
149        );
150        if ($notificationDisabled) {
151            return;
152        }
153
154        $notification = $this->notificationManager->createNotification();
155        $notification
156            ->setApp(AppInfoApplication::APP_ID)
157            ->setObject('signedFile', (string)$signRequest->getId())
158            ->setDateTime((new \DateTime())->setTimestamp($this->timeFactory->now()->getTimestamp())->setTimezone(new \DateTimeZone('UTC')))
159            ->setUser($actorId)
160            ->setSubject('file_signed', [
161                'from' => $this->getFromSignedParameter(
162                    $identifyMethod->getEntity()->getIdentifierKey(),
163                    $identifyMethod->getEntity()->getIdentifierValue(),
164                    $signRequest->getDisplayName(),
165                    $identifyMethod->getEntity()->getId(),
166                ),
167                'file' => [
168                    'type' => 'file',
169                    'id' => (string)$libreSignFile->getNodeId(),
170                    'name' => $libreSignFile->getName(),
171                    'path' => $libreSignFile->getName(),
172                    'link' => $this->url->linkToRouteAbsolute('libresign.page.indexFPath', [
173                        'path' => 'validation/' . $libreSignFile->getUuid(),
174                    ]),
175                ],
176                'signedFile' => [
177                    'type' => 'signer',
178                    'id' => (string)$signRequest->getId(),
179                    'name' => $signRequest->getDisplayName(),
180                ],
181            ]);
182
183        $this->deliveryService->dispatch(new NotificationMessage(
184            $eventId, 'file_signed', 'nextcloud', $libreSignFile, $signRequest,
185            $actorId, new InAppPayload($notification),
186        ), $this->channel);
187    }
188
189    private function sendCanceledNotification(
190        SignRequest $signRequest,
191        FileEntity $libreSignFile,
192        IIdentifyMethod $identifyMethod,
193        string $eventId,
194    ): void {
195        $actor = $this->userSession->getUser();
196        if (!$actor instanceof IUser) {
197            return;
198        }
199        if ($identifyMethod->getEntity()->isDeletedAccount()) {
200            return;
201        }
202        $notificationDisabled = $this->notificationPreferenceResolver->isInAppNotificationDisabled(
203            $identifyMethod->getEntity()->getIdentifierValue(),
204            SignRequestCanceledEvent::SIGN_REQUEST_CANCELED,
205            true,
206        );
207        if ($notificationDisabled) {
208            return;
209        }
210
211        $notification = $this->notificationManager->createNotification();
212        $notification
213            ->setApp(AppInfoApplication::APP_ID)
214            ->setObject('signRequest', (string)$signRequest->getId())
215            ->setDateTime((new \DateTime())->setTimestamp($this->timeFactory->now()->getTimestamp()))
216            ->setUser($identifyMethod->getEntity()->getIdentifierValue())
217            ->setSubject('sign_request_canceled', [
218                'from' => $this->getUserParameter(
219                    $actor->getUID(),
220                    $actor->getDisplayName(),
221                ),
222                'file' => [
223                    'type' => 'file',
224                    'id' => (string)$libreSignFile->getNodeId(),
225                    'name' => $libreSignFile->getName(),
226                    'path' => $libreSignFile->getName(),
227                ],
228                'signRequest' => [
229                    'type' => 'sign-request',
230                    'id' => (string)$signRequest->getId(),
231                    'name' => $actor->getDisplayName(),
232                ],
233            ]);
234
235        $this->deliveryService->dispatch(new NotificationMessage(
236            $eventId, 'sign_request_canceled', 'nextcloud', $libreSignFile, $signRequest,
237            $identifyMethod->getEntity()->getIdentifierValue(), new InAppPayload($notification),
238        ), $this->channel);
239    }
240
241    /**
242     * @psalm-return array{type: 'file', id: string, name: string, path: string, link: string}
243     */
244    protected function getFileParameter(SignRequest $signRequest, FileEntity $libreSignFile): array {
245        if ($signRequest->isObserver()) {
246            $link = $this->url->linkToRouteAbsolute(
247                'libresign.page.indexFPath',
248                ['path' => 'filelist/sign'],
249            );
250            $link .= '?uuid=' . urlencode($libreSignFile->getUuid());
251        } else {
252            $link = $this->url->linkToRouteAbsolute('libresign.page.signFPath', ['uuid' => $signRequest->getUuid(), 'path' => 'pdf']);
253        }
254
255        return [
256            'type' => 'file',
257            'id' => (string)$libreSignFile->getNodeId(),
258            'name' => $libreSignFile->getName(),
259            'path' => $libreSignFile->getName(),
260            'link' => $link,
261        ];
262    }
263
264    protected function getUserParameter(
265        string $userId,
266        string $displayName,
267    ): array {
268        return [
269            'type' => 'user',
270            'id' => $userId,
271            'name' => $displayName,
272        ];
273    }
274
275    protected function getFromSignedParameter(
276        string $type,
277        string $identifier,
278        string $displayName,
279        int $identifyMethodId,
280    ): array {
281
282        if ($type === 'account') {
283            return $this->getUserParameter(
284                $identifier,
285                $displayName
286            );
287        }
288
289        return [
290            'type' => 'signer',
291            'id' => (string)$identifyMethodId,
292            'name' => $displayName,
293        ];
294    }
295}