Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.48% covered (warning)
77.48%
172 / 222
20.00% covered (danger)
20.00%
2 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
NotificationEventSubscriber
77.48% covered (warning)
77.48%
172 / 222
20.00% covered (danger)
20.00%
2 / 10
244.74
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 getSubscribedEvents
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 onFlush
91.18% covered (success)
91.18%
31 / 34
0.00% covered (danger)
0.00%
0 / 1
24.40
 createNotifications
97.83% covered (success)
97.83%
45 / 46
0.00% covered (danger)
0.00%
0 / 1
15
 createNotificationForUsers
93.33% covered (success)
93.33%
28 / 30
0.00% covered (danger)
0.00%
0 / 1
14.06
 saveEmailNotificationForRefOp
41.18% covered (danger)
41.18%
7 / 17
0.00% covered (danger)
0.00%
0 / 1
10.09
 saveEmailNotificationForDPO
92.00% covered (success)
92.00%
23 / 25
0.00% covered (danger)
0.00%
0 / 1
12.07
 saveEmailNotificationForRespTrait
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
90
 getObjectSimpleValue
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
6.03
 getSubjectForNotification
59.26% covered (warning)
59.26%
16 / 27
0.00% covered (danger)
0.00%
0 / 1
54.73
1<?php
2
3declare(strict_types=1);
4
5namespace App\Domain\Notification\Symfony\EventSubscriber\Doctrine;
6
7use App\Application\Interfaces\CollectivityRelated;
8use App\Domain\AIPD\Dictionary\StatutAnalyseImpactDictionary;
9use App\Domain\AIPD\Model\AnalyseImpact;
10use App\Domain\Documentation\Model\Document;
11use App\Domain\Notification\Model\Notification;
12use App\Domain\Notification\Model\NotificationUser;
13use App\Domain\Notification\Serializer\NotificationNormalizer;
14use App\Domain\Registry\Dictionary\MesurementStatusDictionary;
15use App\Domain\Registry\Dictionary\ProofTypeDictionary;
16use App\Domain\Registry\Dictionary\ViolationNatureDictionary;
17use App\Domain\Registry\Model\Contractor;
18use App\Domain\Registry\Model\Mesurement;
19use App\Domain\Registry\Model\Proof;
20use App\Domain\Registry\Model\Request;
21use App\Domain\Registry\Model\Treatment;
22use App\Domain\Registry\Model\Violation;
23use App\Domain\User\Dictionary\UserMoreInfoDictionary;
24use App\Domain\User\Model\Collectivity;
25use App\Domain\User\Model\User;
26use App\Domain\User\Repository\User as UserRepository;
27use App\Infrastructure\ORM\Notification\Repository\Notification as NotificationRepository;
28use App\Infrastructure\ORM\Notification\Repository\NotificationUser as NotificationUserRepository;
29use Doctrine\Common\Collections\ArrayCollection;
30use Doctrine\Common\EventSubscriber;
31use Doctrine\ORM\Event\OnFlushEventArgs;
32use Doctrine\ORM\Events;
33use Symfony\Component\Security\Core\Security;
34use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
35use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
36use Symfony\Contracts\Translation\TranslatorInterface;
37
38/**
39 * This subscriber handles events that are generated by doctrine, and creates notifications from them if necessary.
40 */
41class NotificationEventSubscriber implements EventSubscriber
42{
43    protected array $classes = [
44        AnalyseImpact::class,
45        Treatment::class,
46        Mesurement::class,
47        Violation::class,
48        Proof::class,
49        Contractor::class,
50        Request::class,
51        Document::class,
52    ];
53
54    protected array $recipients = [
55        AnalyseImpact::class => Notification::NOTIFICATION_COLLECTIVITY | Notification::NOTIFICATION_DPO,
56        Treatment::class     => Notification::NOTIFICATION_DPO,
57        Mesurement::class    => Notification::NOTIFICATION_DPO,
58        Violation::class     => Notification::NOTIFICATION_DPO,
59        Proof::class         => Notification::NOTIFICATION_DPO,
60        Contractor::class    => Notification::NOTIFICATION_DPO,
61        Request::class       => Notification::NOTIFICATION_DPO,
62        Document::class      => Notification::NOTIFICATION_COLLECTIVITY,
63    ];
64
65    protected NotificationRepository $notificationRepository;
66    protected NotificationUserRepository $notificationUserRepository;
67    protected \App\Infrastructure\ORM\AIPD\Repository\AnalyseImpact $aipdRepository;
68    protected UserRepository $userRepository;
69    protected Security $security;
70    protected NormalizerInterface $normalizer;
71    protected TranslatorInterface $translator;
72    protected string $requestDays;
73    protected string $surveyDays;
74
75    public function __construct(
76        NotificationRepository $notificationRepository,
77        NotificationNormalizer $normalizer,
78        UserRepository $userRepository,
79        NotificationUserRepository $notificationUserRepository,
80        \App\Infrastructure\ORM\AIPD\Repository\AnalyseImpact $aipdRepository,
81        Security $security,
82        TranslatorInterface $translator,
83        string $requestDays,
84        string $surveyDays,
85    ) {
86        $this->notificationRepository     = $notificationRepository;
87        $this->normalizer                 = $normalizer;
88        $this->userRepository             = $userRepository;
89        $this->notificationUserRepository = $notificationUserRepository;
90        $this->aipdRepository             = $aipdRepository;
91        $this->security                   = $security;
92        $this->translator                 = $translator;
93        $this->requestDays                = $requestDays;
94        $this->surveyDays                 = $surveyDays;
95    }
96
97    public function getSubscribedEvents(): array
98    {
99        return [
100            Events::onFlush,
101        ];
102    }
103
104    public function onFlush(OnFlushEventArgs $eventArgs)
105    {
106        /** @var User|null $user */
107        $user = $this->security->getUser();
108
109        if (isset($user) && is_object($user) && User::class === get_class($user) && $user->isNotGeneratesNotifications()) {
110            // User does not generate notifications, exit now
111            return;
112        }
113        $em  = $eventArgs->getObjectManager();
114        $uow = $em->getUnitOfWork();
115
116        foreach ($uow->getScheduledEntityInsertions() as $entity) {
117            $class = get_class($entity);
118            if (!in_array($class, $this->classes) || Request::class === $class || AnalyseImpact::class === $class) {
119                continue;
120            }
121
122            $this->createNotifications($entity, 'create', $em);
123        }
124
125        foreach ($uow->getScheduledEntityUpdates() as $entity) {
126            $class = get_class($entity);
127
128            if (!in_array($class, $this->classes) || Document::class === $class) {
129                continue;
130            }
131            $action = 'update';
132            if (Request::class === $class) {
133                $ch = $uow->getEntityChangeSet($entity);
134                // Exit if the request has no state change
135                if (!isset($ch['state'])) {
136                    continue;
137                }
138                $action = 'state_change';
139            }
140
141            if (AnalyseImpact::class === $class) {
142                $ch = $uow->getEntityChangeSet($entity);
143                // Exit if the request has no state change
144                if (!isset($ch['statut']) && !isset($ch['isReadyForValidation'])) {
145                    continue;
146                }
147
148                if (isset($ch['statut'])) {
149                    $action = 'validated';
150                } elseif (isset($ch['isReadyForValidation'])) {
151                    $action = 'validation';
152                }
153            }
154
155            $this->createNotifications($entity, $action, $em);
156        }
157        foreach ($uow->getScheduledEntityDeletions() as $entity) {
158            $class = get_class($entity);
159            if (!in_array($class, $this->classes) || Request::class == $class || Document::class === $class || AnalyseImpact::class === $class) {
160                continue;
161            }
162            $this->createNotifications($entity, 'delete', $em);
163        }
164    }
165
166    private function createNotifications($object, $action, $em): array
167    {
168        $notifications = [];
169        $recipients    = $this->recipients[get_class($object)];
170
171        if (AnalyseImpact::class === get_class($object) && 'validation' === $action) {
172            // DO not send status change of AIPD to collectivity
173            $recipients = Notification::NOTIFICATION_DPO;
174        }
175
176        $uow = $em->getUnitOfWork();
177
178        $normalized = $this->normalizer->normalize($object, null,
179            [
180                AbstractObjectNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($o) {
181                    return $this->getObjectSimpleValue($o);
182                },
183                'maxDepth'                                         => 1,
184                AbstractObjectNormalizer::ENABLE_MAX_DEPTH         => true,
185                AbstractObjectNormalizer::CIRCULAR_REFERENCE_LIMIT => 1,
186                AbstractObjectNormalizer::MAX_DEPTH_HANDLER        => function ($o) {
187                    if (is_iterable($o)) {
188                        $d = [];
189                        foreach ($o as $item) {
190                            $d[] = $this->getObjectSimpleValue($item);
191                        }
192
193                        return $d;
194                    }
195
196                    return $this->getObjectSimpleValue($o);
197                },
198            ],
199        );
200
201        if ($recipients & Notification::NOTIFICATION_DPO) {
202            $notification    = $this->createNotificationForUsers($object, $action, $normalized);
203            $notifications[] = $notification;
204        }
205
206        if ($recipients & Notification::NOTIFICATION_COLLECTIVITY) {
207            // get all non-DPO users
208            $collectivity = method_exists($object, 'getCollectivity') ? $object->getCollectivity() : null;
209
210            if (AnalyseImpact::class === get_class($object) && $object->getConformiteTraitement() && $object->getConformiteTraitement()->getTraitement()) {
211                $collectivity = $object->getConformiteTraitement()->getTraitement()->getCollectivity();
212            }
213
214            if ($collectivity) {
215                $users = $this->userRepository->findNonDpoUsersForCollectivity($collectivity);
216            } else {
217                $users = $this->userRepository->findNonDpoUsers();
218            }
219
220            $notification    = $this->createNotificationForUsers($object, $action, $normalized, $users);
221            $notifications[] = $notification;
222        }
223
224        // Insert notifications and notification_users
225
226        $meta  = $em->getClassMetadata(Notification::class);
227        $meta2 = $em->getClassMetadata(NotificationUser::class);
228
229        foreach ($notifications as $notif) {
230            $notif->setSubject($this->getSubjectForNotification($notif));
231            $em->persist($notif);
232            /**
233             * @var Notification $notif
234             */
235            if ($notif->getNotificationUsers()) {
236                foreach ($notif->getNotificationUsers() as $u) {
237                    $em->persist($u);
238                    $uow->computeChangeSet($meta2, $u);
239                }
240            }
241
242            $uow->computeChangeSet($meta, $notif);
243        }
244
245        return $notifications;
246    }
247
248    private function createNotificationForUsers($object, $action, $normalized, $users = null): Notification
249    {
250        $notification = new Notification();
251        $mod          = Notification::MODULES[get_class($object)];
252        $recipients   = $this->recipients[get_class($object)];
253        $notification->setModule('notification.modules.' . $mod);
254        $collectivity = method_exists($object, 'getCollectivity') ? $object->getCollectivity() : null;
255
256        if (AnalyseImpact::class === get_class($object) && $object->getConformiteTraitement() && $object->getConformiteTraitement()->getTraitement()) {
257            $collectivity = $object->getConformiteTraitement()->getTraitement()->getCollectivity();
258        }
259
260        $user = $this->security->getUser();
261
262        if ($recipients & Notification::NOTIFICATION_DPO) {
263            $notification->setDpo(true);
264        }
265
266        $notification->setCollectivity($collectivity);
267        $notification->setName(method_exists($object, 'getName') ? $object->getName() : $object->__toString());
268        $notification->setAction('notification.actions.' . $action);
269        if ($user && is_object($user) && User::class === get_class($user)) {
270            $notification->setCreatedBy($user);
271        }
272
273        $notification->setObject((object) $normalized);
274
275        $nus = [];
276
277        if ($users) {
278            $nus = $this->notificationUserRepository->saveUsers($notification, $users);
279
280            $notification->setNotificationUsers($nus);
281        } else {
282            if (AnalyseImpact::class === get_class($object)) {
283                $nus = $this->saveEmailNotificationForDPO($notification, $object->getConformiteTraitement()->getTraitement()->getCollectivity());
284                $notification->setNotificationUsers($nus);
285            }
286        }
287
288        if (Document::class === get_class($object)) {
289            $newnus = array_merge($this->saveEmailNotificationForRefOp($notification), $nus);
290            $notification->setNotificationUsers($newnus);
291        }
292        if (Violation::class === get_class($object)) {
293            $newnus = array_merge($this->saveEmailNotificationForRespTrait($notification, $object), $nus);
294            $notification->setNotificationUsers($newnus);
295        }
296
297        return $notification;
298    }
299
300    private function saveEmailNotificationForRefOp(Notification $notification): array
301    {
302        $nus = [];
303        // Get referent operationnels for this collectivity
304        $refs = (new ArrayCollection($this->userRepository->findAll()))->filter(function (User $u) {
305            $mi = $u->getMoreInfos();
306
307            return $mi && isset($mi[UserMoreInfoDictionary::MOREINFO_OPERATIONNAL]) && $mi[UserMoreInfoDictionary::MOREINFO_OPERATIONNAL];
308        });
309
310        // Add notification with email address for the référents
311        foreach ($refs as $ref) {
312            $nu = new NotificationUser();
313            if (User::class === get_class($ref)) {
314                $nu->setMail($ref->getEmail());
315                $nu->setUser($ref);
316            } else {
317                $nu->setMail($ref);
318            }
319
320            $nu->setNotification($notification);
321            $nu->setActive(false);
322            $nu->setToken(sha1($notification->getName() . microtime() . $nu->getMail()));
323            $nu->setSent(false);
324            $nus[] = $nu;
325        }
326
327        return $nus;
328    }
329
330    private function saveEmailNotificationForDPO(Notification $notification, Collectivity $collectivity): array
331    {
332        // Get DPOS
333        $refs = (new ArrayCollection($this->userRepository->findAll()))->filter(function (User $u) use ($collectivity) {
334            $mi = $u->getMoreInfos();
335
336            $refColIds = [];
337            /** @var Collectivity $col */
338            foreach ($u->getCollectivitesReferees() as $col) {
339                $refColIds[] = $col->getId()->toString();
340            }
341
342            return in_array('ROLE_ADMIN', $u->getRoles())
343                || (in_array('ROLE_REFERENT', $u->getRoles()) && in_array($collectivity->getId()->toString(), $refColIds))
344                || ($mi && isset($mi[UserMoreInfoDictionary::MOREINFO_DPD]) && $mi[UserMoreInfoDictionary::MOREINFO_DPD]);
345        });
346
347        // Also get DPOs from collectivity
348        if ($collectivity->getDpo()) {
349            if ($collectivity->getDpo()->getNotification()) {
350                $refs[] = $collectivity->getDpo()->getMail();
351            }
352        }
353
354        $nus = [];
355        // Add notification with email address for the référents
356        foreach ($refs as $ref) {
357            $nu = new NotificationUser();
358            if (is_object($ref) && $ref instanceof User) {
359                $nu->setMail($ref->getEmail());
360                $nu->setUser($ref);
361            } else {
362                $nu->setMail($ref);
363            }
364
365            $nu->setNotification($notification);
366            $nu->setActive(false);
367            $nu->setToken(sha1($notification->getName() . microtime() . $nu->getMail()));
368            $nu->setSent(false);
369            $nus[] = $nu;
370        }
371
372        return $nus;
373    }
374
375    private function saveEmailNotificationForRespTrait(Notification $notification, CollectivityRelated $object): array
376    {
377        $nus = [];
378        // Get referent operationnels for this collectivity
379        $refs = $object->getCollectivity()->getUsers()->filter(function (User $u) {
380            $mi = $u->getMoreInfos();
381
382            return $mi && isset($mi[UserMoreInfoDictionary::MOREINFO_TREATMENT]) && $mi[UserMoreInfoDictionary::MOREINFO_TREATMENT];
383        });
384        if (0 === $refs->count()) {
385            // No ref OP, get from collectivity
386            if ($object->getCollectivity() && $object->getCollectivity()->getLegalManager()) {
387                $refs = [$object->getCollectivity()->getLegalManager()->getMail()];
388            }
389        }
390
391        foreach ($refs as $ref) {
392            $nu = new NotificationUser();
393            if (is_object($ref) && User::class === get_class($ref)) {
394                $nu->setMail($ref->getEmail());
395                $nu->setUser($ref);
396            } else {
397                $nu->setMail($ref);
398            }
399
400            $nu->setToken(sha1($notification->getName() . microtime() . $nu->getMail()));
401            $nu->setNotification($notification);
402            $nu->setActive(false);
403            $nu->setSent(false);
404            $nus[] = $nu;
405            //            $this->notificationUserRepository->persist($nu);
406        }
407
408        return $nus;
409    }
410
411    private function getObjectSimpleValue($object)
412    {
413        if (is_object($object)) {
414            if (method_exists($object, 'getId')) {
415                return $object->getId();
416            } elseif (method_exists($object, '__toString')) {
417                return $object->__toString();
418            } elseif (method_exists($object, 'format')) {
419                return $object->format(DATE_ATOM);
420            }
421
422            return '';
423        }
424
425        if (is_array($object)) {
426            return join(', ', $object);
427        }
428
429        return $object;
430    }
431
432    private function getSubjectForNotification(Notification $notification): string
433    {
434        if (
435            'notification.modules.violation' === $notification->getModule()
436        ) {
437            $ob = $notification->getObject();
438            if (isset($ob->violationNatures)) {
439                $vn = explode(',', $ob->violationNatures);
440                if (is_array($vn) && count($vn) > 0) {
441                    return join(', ', array_map(function ($v) {
442                        return ViolationNatureDictionary::getNatures()[trim($v)] ?? '';
443                    }, $vn));
444                }
445            } elseif (isset($ob->violationNature)) {
446                return ViolationNatureDictionary::getNatures()[$ob->violationNature] ?? '';
447            }
448
449            return '';
450        }
451
452        if (
453            'notification.modules.proof' === $notification->getModule()
454        ) {
455            $type = $notification->getObject()->type;
456
457            return $type && isset(ProofTypeDictionary::getTypes()[$type]) ? ProofTypeDictionary::getTypes()[$type] : '';
458        }
459
460        if ('notification.modules.protect_action' === $notification->getModule()) {
461            return MesurementStatusDictionary::getStatus()[$notification->getObject()->status] ?? '';
462        }
463
464        if ('notification.modules.request' === $notification->getModule() && ('notifications.actions.state_change' === $notification->getAction() || 'notification.actions.state_change' === $notification->getAction())) {
465            return $notification->getObject()->state;
466        }
467
468        if ('notification.modules.aipd' === $notification->getModule() && ('notifications.actions.state_change' === $notification->getAction() || 'notification.actions.state_change' === $notification->getAction())) {
469            return StatutAnalyseImpactDictionary::getStatuts()[$notification->getObject()->statut];
470        }
471
472        if ('notification.modules.aipd' === $notification->getModule() && ('notifications.actions.validated' === $notification->getAction() || 'notification.actions.validated' === $notification->getAction())) {
473            return StatutAnalyseImpactDictionary::getStatuts()[$notification->getObject()->statut];
474        }
475
476        if ('notification.modules.document' === $notification->getModule()) {
477            return $notification->getObject()->typeName;
478        }
479
480        if ('notifications.actions.validation' === $notification->getAction() || 'notification.actions.validation' === $notification->getAction()) {
481            return $this->translator->trans('notifications.subject.validation');
482        }
483
484        return '';
485    }
486}