Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
82.00% covered (warning)
82.00%
82 / 100
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
MailNotifyListener
82.00% covered (warning)
82.00%
82 / 100
42.86% covered (danger)
42.86%
3 / 7
32.57
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
1
 sendSignMailNotification
83.33% covered (warning)
83.33%
25 / 30
0.00% covered (danger)
0.00%
0 / 1
8.30
 sendSignedMailNotification
70.00% covered (warning)
70.00%
14 / 20
0.00% covered (danger)
0.00%
0 / 1
6.97
 sendCanceledMailNotification
76.92% covered (warning)
76.92%
20 / 26
0.00% covered (danger)
0.00%
0 / 1
11.23
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\Db\File as FileEntity;
12use OCA\Libresign\Db\SignRequest;
13use OCA\Libresign\Events\SendSignNotificationEvent;
14use OCA\Libresign\Events\SignedEvent;
15use OCA\Libresign\Events\SignRequestCanceledEvent;
16use OCA\Libresign\Exception\LibresignException;
17use OCA\Libresign\Service\IdentifyMethod\IdentifyService;
18use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod;
19use OCA\Libresign\Service\Notification\MailChannel;
20use OCA\Libresign\Service\Notification\MailPayload;
21use OCA\Libresign\Service\Notification\NotificationHistoryService;
22use OCA\Libresign\Service\Notification\NotificationMessage;
23use OCA\Libresign\Service\Notification\NotificationModule;
24use OCA\Libresign\Service\NotificationDeliveryService;
25use OCA\Libresign\Service\NotificationPreferenceResolver;
26use OCP\EventDispatcher\Event;
27use OCP\IUser;
28use OCP\IUserManager;
29use OCP\IUserSession;
30use Psr\Log\LoggerInterface;
31
32class MailNotifyListener implements NotificationModule {
33    public function __construct(
34        protected IUserSession $userSession,
35        protected IUserManager $userManager,
36        protected IdentifyService $identifyService,
37        private MailChannel $mailChannel,
38        private LoggerInterface $logger,
39        private NotificationPreferenceResolver $notificationPreferenceResolver,
40        private NotificationDeliveryService $deliveryService,
41        private NotificationHistoryService $history,
42    ) {
43    }
44
45    #[\Override]
46    public function transport(): \OCA\Libresign\Service\Notification\NotificationChannel {
47        return $this->mailChannel;
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        /** @var SendSignNotificationEvent|SignedEvent|SignRequestCanceledEvent $event */
59        match ($event::class) {
60            SendSignNotificationEvent::class => $this->sendSignMailNotification(
61                $event->getSignRequest(),
62                $event->getIdentifyMethod(),
63                $event->getLibreSignFile(),
64                $event->getDeliveryEventId(),
65            ),
66            SignedEvent::class => $this->sendSignedMailNotification(
67                $event->getSignRequest(),
68                $event->getIdentifyMethod(),
69                $event->getLibreSignFile(),
70                $event->getUser(),
71                $event->getDeliveryEventId(),
72            ),
73            SignRequestCanceledEvent::class => $this->sendCanceledMailNotification(
74                $event->getSignRequest(),
75                $event->getIdentifyMethod(),
76                $event->getLibreSignFile(),
77                $event->getDeliveryEventId(),
78            ),
79        };
80    }
81
82    protected function sendSignMailNotification(
83        SignRequest $signRequest,
84        IIdentifyMethod $identifyMethod,
85        FileEntity $file,
86        string $eventId,
87    ): void {
88        try {
89            if ($identifyMethod->getEntity()->isDeletedAccount()) {
90                return;
91            }
92            $email = '';
93            if ($identifyMethod->getName() === 'account') {
94                $userId = $identifyMethod->getEntity()->getIdentifierValue();
95                $email = $this->userManager->get($userId)?->getEMailAddress() ?? '';
96            } elseif ($identifyMethod->getName() === 'email') {
97                $email = $identifyMethod->getEntity()->getIdentifierValue();
98            }
99            if (empty($email)) {
100                return;
101            }
102
103            $users = $this->userManager->getByEmail($email);
104            if (count($users) === 1) {
105                $userId = $users[0]->getUID();
106                if ($this->notificationPreferenceResolver->isEmailNotificationDisabled(
107                    $userId,
108                    SendSignNotificationEvent::FILE_TO_SIGN,
109                    true,
110                )) {
111                    return;
112                }
113            }
114
115            $this->deliveryService->dispatch(
116                new NotificationMessage(
117                    $eventId, 'sign_request', 'mail', $file, $signRequest, $email,
118                    new MailPayload(firstNotification: $this->history->isFirst($signRequest, 'mail')),
119                    'mail',
120                ),
121                $this->mailChannel,
122            );
123        } catch (\InvalidArgumentException $e) {
124            $this->logger->error($e->getMessage(), ['exception' => $e]);
125            return;
126        }
127    }
128
129    protected function sendSignedMailNotification(
130        SignRequest $signRequest,
131        IIdentifyMethod $identifyMethod,
132        FileEntity $libreSignFile,
133        IUser $user,
134        string $eventId,
135    ): void {
136        try {
137            if ($libreSignFile->hasParent()) {
138                return;
139            }
140            if ($identifyMethod->getEntity()->isDeletedAccount()) {
141                return;
142            }
143            if ($this->notificationPreferenceResolver->isEmailNotificationDisabled(
144                $libreSignFile->getUserId(),
145                SignedEvent::FILE_SIGNED,
146                true,
147            )) {
148                return;
149            }
150
151            $email = $user->getEMailAddress();
152
153            if (empty($email)) {
154                return;
155            }
156
157            $this->deliveryService->dispatch(
158                new NotificationMessage($eventId, 'file_signed', 'mail', $libreSignFile, $signRequest, $email, new MailPayload($user->getDisplayName())),
159                $this->mailChannel,
160            );
161
162        } catch (\InvalidArgumentException $e) {
163            $this->logger->error($e->getMessage(), ['exception' => $e]);
164            return;
165        }
166    }
167
168    protected function sendCanceledMailNotification(
169        SignRequest $signRequest,
170        IIdentifyMethod $identifyMethod,
171        FileEntity $libreSignFile,
172        string $eventId,
173    ): void {
174        try {
175            if ($identifyMethod->getEntity()->isDeletedAccount()) {
176                return;
177            }
178
179            $email = '';
180            if ($identifyMethod->getName() === 'account') {
181                $userId = $identifyMethod->getEntity()->getIdentifierValue();
182                $user = $this->userManager->get($userId);
183                if ($user) {
184                    $email = $user->getEMailAddress();
185                }
186            } elseif ($identifyMethod->getName() === 'email') {
187                $email = $identifyMethod->getEntity()->getIdentifierValue();
188            }
189
190            if (empty($email)) {
191                return;
192            }
193
194            $users = $this->userManager->getByEmail($email);
195            if (count($users) === 1) {
196                $userId = $users[0]->getUID();
197                if ($this->notificationPreferenceResolver->isEmailNotificationDisabled($userId, SignRequestCanceledEvent::SIGN_REQUEST_CANCELED, true)) {
198                    return;
199                }
200            }
201
202            try {
203                $this->deliveryService->dispatch(
204                    new NotificationMessage($eventId, 'sign_request_canceled', 'mail', $libreSignFile, $signRequest, $email, new MailPayload()),
205                    $this->mailChannel,
206                );
207            } catch (LibresignException) {
208                // Mail failures must not undo an otherwise committed cancellation.
209                return;
210            }
211
212        } catch (\InvalidArgumentException $e) {
213            $this->logger->error($e->getMessage(), ['exception' => $e]);
214            return;
215        }
216    }
217}