Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.82% covered (warning)
81.82%
126 / 154
41.67% covered (danger)
41.67%
5 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
LogJournalDoctrineSubscriber
81.82% covered (warning)
81.82%
126 / 154
41.67% covered (danger)
41.67%
5 / 12
61.85
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getSubscribedEvents
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 postPersist
40.00% covered (danger)
40.00%
6 / 15
0.00% covered (danger)
0.00%
0 / 1
13.78
 postUpdate
42.86% covered (danger)
42.86%
9 / 21
0.00% covered (danger)
0.00%
0 / 1
56.98
 preRemove
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 registerLog
89.47% covered (warning)
89.47%
17 / 19
0.00% covered (danger)
0.00%
0 / 1
3.01
 registerDeleteLog
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
3.00
 supports
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getCollectivity
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 registerLogForUser
95.00% covered (success)
95.00%
38 / 40
0.00% covered (danger)
0.00%
0 / 1
9
 registerLogSoftDelete
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
2.00
 notConcernedByDeletionLog
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * This file is part of the MADIS - RGPD Management application.
5 *
6 * @copyright Copyright (c) 2018-2019 Soluris - Solutions Numériques Territoriales Innovantes
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22declare(strict_types=1);
23
24namespace App\Domain\Reporting\Symfony\EventSubscriber\Doctrine;
25
26use App\Domain\Registry\Model\ConformiteOrganisation\Conformite;
27use App\Domain\Registry\Model\ConformiteOrganisation\Participant;
28use App\Domain\Registry\Model\ConformiteTraitement\Reponse;
29use App\Domain\Registry\Model\Proof;
30use App\Domain\Registry\Model\Request;
31use App\Domain\Registry\Model\Violation;
32use App\Domain\Reporting\Dictionary\LogJournalActionDictionary;
33use App\Domain\Reporting\Dictionary\LogJournalSubjectDictionary;
34use App\Domain\Reporting\Model\LoggableSubject;
35use App\Domain\Reporting\Model\LogJournal;
36use App\Domain\Reporting\Symfony\EventSubscriber\Event\LogJournalEvent;
37use App\Domain\User\Model\Collectivity;
38use App\Domain\User\Model\ComiteIlContact;
39use App\Domain\User\Model\Service;
40use App\Domain\User\Model\User;
41use Doctrine\Common\EventSubscriber;
42use Doctrine\ORM\EntityManagerInterface;
43use Doctrine\ORM\Event\PostPersistEventArgs;
44use Doctrine\ORM\Event\PostUpdateEventArgs;
45use Doctrine\ORM\Event\PreRemoveEventArgs;
46use Doctrine\ORM\Events;
47use Symfony\Component\Cache\Adapter\AdapterInterface;
48use Symfony\Component\Cache\Adapter\ArrayAdapter;
49use Symfony\Component\EventDispatcher\EventDispatcherInterface;
50use Symfony\Component\HttpFoundation\RequestStack;
51use Symfony\Component\Security\Core\Security;
52
53class LogJournalDoctrineSubscriber implements EventSubscriber
54{
55    /**
56     * @var Security
57     */
58    private $security;
59
60    /**
61     * @var EventDispatcherInterface
62     */
63    private $eventDispatcher;
64
65    /**
66     * @var EntityManagerInterface
67     */
68    private $entityManager;
69
70    /**
71     * To avoid multiple line on same object register objects in folder.
72     *
73     * @var AdapterInterface
74     */
75    private $cacheAdapter;
76
77    /**
78     * @var RequestStack
79     */
80    private $requestStack;
81
82    public function __construct(
83        Security $security,
84        EventDispatcherInterface $eventDispatcher,
85        EntityManagerInterface $entityManager,
86        ArrayAdapter $cacheAdapter,
87        RequestStack $requestStack,
88    ) {
89        $this->security        = $security;
90        $this->eventDispatcher = $eventDispatcher;
91        $this->entityManager   = $entityManager;
92        $this->cacheAdapter    = $cacheAdapter;
93        $this->requestStack    = $requestStack;
94    }
95
96    public function getSubscribedEvents(): array
97    {
98        return [
99            Events::postPersist,
100            Events::postUpdate,
101            Events::preRemove,
102        ];
103    }
104
105    public function postPersist(PostPersistEventArgs $args): void
106    {
107        $object = $args->getObject();
108        if (!$this->supports($object)) {
109            return;
110        }
111
112        $action = LogJournalActionDictionary::CREATE;
113
114        switch (\get_class($args->getObject())) {
115            // l'ajout d'un participant ou d'une conformité entraine la modification de l'évaluation
116            case Conformite::class:
117            case Participant::class:
118                /** @var Conformite|Participant $object */
119                $object = $object->getEvaluation();
120                $action = LogJournalActionDictionary::UPDATE;
121                break;
122                // l'ajout d'un contact IL ou d'un service entraine la modification de la structure
123            case ComiteIlContact::class:
124                /** @var ComiteIlContact $object */
125                $object = $object->getCollectivity();
126                $action = LogJournalActionDictionary::UPDATE;
127                break;
128                // l'ajout d'une réponse entraine la modification de la conformité du traitement
129            case Reponse::class:
130                /** @var Reponse $object */
131                $object = $object->getConformiteTraitement();
132                $action = LogJournalActionDictionary::UPDATE;
133                break;
134        }
135
136        $this->registerLog($object, $action);
137    }
138
139    public function postUpdate(PostUpdateEventArgs $args): void
140    {
141        $object = $args->getObject();
142        if (!($object instanceof LoggableSubject) || !$this->supports($object)) {
143            return;
144        }
145
146        // specific case for user. Need to know which data is update
147        switch (get_class($object)) {
148            case User::class:
149                $this->registerLogForUser($object);
150                break;
151            case Proof::class:
152            case Request::class:
153            case Violation::class:
154                $this->registerLogSoftDelete($object);
155                break;
156            case Conformite::class:
157            case Participant::class:
158                $this->registerLog($object->getEvaluation(), LogJournalActionDictionary::UPDATE);
159                break;
160            case ComiteIlContact::class:
161                $this->registerLog($object->getCollectivity(), LogJournalActionDictionary::UPDATE);
162                break;
163            case Reponse::class:
164                /** @var User $user */
165                $user       = $this->security->getUser();
166                $item       = $this->cacheAdapter->getItem('already_register_item-' . $user->getId()->toString());
167                $request    = $this->requestStack->getCurrentRequest();
168                $conformite = $object->getConformiteTraitement();
169                $id         = $request->get('id');
170                // Cas spéciale lors de l'édition du traitement lors de la modification d'une conformité d'un traitement
171                if (\is_null($item->get()) || $id === $item->get()->toString() && $conformite->getId()->toString() === $id) {
172                    $this->registerLog($conformite, LogJournalActionDictionary::UPDATE);
173                }
174                break;
175            default:
176                $this->registerLog($object, LogJournalActionDictionary::UPDATE);
177        }
178    }
179
180    public function preRemove(PreRemoveEventArgs $args): void
181    {
182        if (!$this->supports($args->getObject())) {
183            return;
184        }
185
186        $this->registerDeleteLog($args);
187    }
188
189    private function registerLog(LoggableSubject $object, string $action)
190    {
191        /** @var User $user */
192        $user = $this->security->getUser();
193        $item = $this->cacheAdapter->getItem('already_register_item-' . $user->getId()->toString());
194        // si la clef de l'objet existe déja alors on ajoute pas de log.
195        if (!$item->isHit()) {
196            $item->expiresAfter(2);
197            $item->set($object->getId());
198            $this->cacheAdapter->save($item);
199        } elseif ($object->getId()->toString() === $item->get()->toString()) {
200            return;
201        }
202
203        $subject = LogJournalSubjectDictionary::getSubjectFromClassName(\get_class($object));
204
205        $log = new LogJournal(
206            $this->getCollectivity($object),
207            $user->getFullName(),
208            $user->getEmail(),
209            $action,
210            $subject,
211            $object->getId()->toString(),
212            $object->__toString()
213        );
214        $this->eventDispatcher->dispatch(new LogJournalEvent($log));
215    }
216
217    private function registerDeleteLog(PreRemoveEventArgs $args)
218    {
219        $object = $args->getObject();
220
221        if (!($object instanceof LoggableSubject) || $this->notConcernedByDeletionLog($object)) {
222            return;
223        }
224
225        /** @var User $user */
226        $user = $this->security->getUser();
227
228        $subject = LogJournalSubjectDictionary::getSubjectFromClassName(\get_class($object));
229
230        $log = new LogJournal(
231            $this->getCollectivity($object),
232            $user->getFullName(),
233            $user->getEmail(),
234            LogJournalActionDictionary::DELETE,
235            $subject,
236            $object->getId()->toString(),
237            $object->__toString()
238        );
239
240        $this->eventDispatcher->dispatch(new LogJournalEvent($log, $object));
241    }
242
243    private function supports($object): bool
244    {
245        return $object instanceof LoggableSubject && !\is_null($this->security->getUser());
246    }
247
248    private function getCollectivity($object)
249    {
250        if (Collectivity::class === \get_class($object)) {
251            return $object;
252        }
253
254        if (\method_exists($object, 'getCollectivity')) {
255            return $object->getCollectivity();
256        }
257
258        /** @var User $user */
259        $user = $this->security->getUser();
260
261        return $user->getCollectivity();
262    }
263
264    private function registerLogForUser(LoggableSubject $object)
265    {
266        /** @var User $user */
267        $user    = $this->security->getUser();
268        $changes = $this->entityManager->getUnitOfWork()->getEntityChangeSet($object);
269
270        // don't need to add log on lastLogin because already register in LoginSubscriber
271        if (\array_key_exists('lastlogin', $changes)) {
272            return;
273        }
274
275        $collectivity = $this->getCollectivity($object);
276        if (\array_key_exists('deletedAt', $changes)) {
277            $action = LogJournalActionDictionary::SOFT_DELETE;
278            if (!\is_null($changes['deletedAt'][0])) {
279                $action = LogJournalActionDictionary::SOFT_DELETE_REVOKE;
280            }
281            $log = new LogJournal(
282                $collectivity,
283                $user->getFullName(),
284                $user->getEmail(),
285                $action,
286                LogJournalSubjectDictionary::USER_USER,
287                $object->getId()->toString(),
288                $object->__toString()
289            );
290            $this->eventDispatcher->dispatch(new LogJournalEvent($log));
291
292            return;
293        }
294
295        $subjectTypes = [];
296        if (\array_key_exists('firstName', $changes)) {
297            $subjectTypes[] = LogJournalSubjectDictionary::USER_FIRSTNAME;
298        }
299
300        if (\array_key_exists('lastName', $changes)) {
301            $subjectTypes[] = LogJournalSubjectDictionary::USER_LASTNAME;
302        }
303
304        if (\array_key_exists('email', $changes)) {
305            $subjectTypes[] = LogJournalSubjectDictionary::USER_EMAIL;
306        }
307
308        if (\array_key_exists('password', $changes)) {
309            $subjectTypes[] = LogJournalSubjectDictionary::USER_PASSWORD;
310        }
311
312        foreach ($subjectTypes as $subjectType) {
313            $log = new LogJournal(
314                $collectivity,
315                $user->getFullName(),
316                $user->getEmail(),
317                LogJournalActionDictionary::UPDATE,
318                $subjectType,
319                $object->getId()->toString(),
320                $object->__toString()
321            );
322            $this->eventDispatcher->dispatch(new LogJournalEvent($log));
323        }
324    }
325
326    private function registerLogSoftDelete(LoggableSubject $object)
327    {
328        /** @var User $user */
329        $user         = $this->security->getUser();
330        $changes      = $this->entityManager->getUnitOfWork()->getEntityChangeSet($object);
331        $collectivity = $this->getCollectivity($object);
332
333        // if is not a softdelete request, just register a log update
334        $action = LogJournalActionDictionary::UPDATE;
335        if (\array_key_exists('deletedAt', $changes)) {
336            $action = LogJournalActionDictionary::SOFT_DELETE;
337        }
338
339        $subject = LogJournalSubjectDictionary::getSubjectFromClassName(\get_class($object));
340        $log     = new LogJournal(
341            $collectivity,
342            $user->getFullName(),
343            $user->getEmail(),
344            $action,
345            $subject,
346            $object->getId()->toString(),
347            $object->__toString()
348        );
349        $this->eventDispatcher->dispatch(new LogJournalEvent($log));
350    }
351
352    private function notConcernedByDeletionLog(LoggableSubject $subject): bool
353    {
354        return \in_array(\get_class($subject), [
355            Conformite::class,
356            Participant::class,
357            ComiteIlContact::class,
358            Service::class,
359            Reponse::class,
360        ]);
361    }
362}