Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.12% covered (warning)
83.12%
128 / 154
50.00% covered (danger)
50.00%
6 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
LogJournalDoctrineSubscriber
83.12% covered (warning)
83.12%
128 / 154
50.00% covered (danger)
50.00%
6 / 12
56.18
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
50.57
 preRemove
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 registerLog
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
3
 registerDeleteLog
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
2.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\LifecycleEventArgs;
44use Doctrine\ORM\Events;
45use Symfony\Component\Cache\Adapter\AdapterInterface;
46use Symfony\Component\Cache\Adapter\ArrayAdapter;
47use Symfony\Component\EventDispatcher\EventDispatcherInterface;
48use Symfony\Component\HttpFoundation\RequestStack;
49use Symfony\Component\Security\Core\Security;
50
51class LogJournalDoctrineSubscriber implements EventSubscriber
52{
53    /**
54     * @var Security
55     */
56    private $security;
57
58    /**
59     * @var EventDispatcherInterface
60     */
61    private $eventDispatcher;
62
63    /**
64     * @var EntityManagerInterface
65     */
66    private $entityManager;
67
68    /**
69     * To avoid multiple line on same object register objects in folder.
70     *
71     * @var AdapterInterface
72     */
73    private $cacheAdapter;
74
75    /**
76     * @var RequestStack
77     */
78    private $requestStack;
79
80    public function __construct(
81        Security $security,
82        EventDispatcherInterface $eventDispatcher,
83        EntityManagerInterface $entityManager,
84        ArrayAdapter $cacheAdapter,
85        RequestStack $requestStack,
86    ) {
87        $this->security        = $security;
88        $this->eventDispatcher = $eventDispatcher;
89        $this->entityManager   = $entityManager;
90        $this->cacheAdapter    = $cacheAdapter;
91        $this->requestStack    = $requestStack;
92    }
93
94    public function getSubscribedEvents()
95    {
96        return [
97            Events::postPersist,
98            Events::postUpdate,
99            Events::preRemove,
100        ];
101    }
102
103    public function postPersist(LifecycleEventArgs $args): void
104    {
105        $object = $args->getObject();
106        if (!$this->supports($object)) {
107            return;
108        }
109
110        $action = LogJournalActionDictionary::CREATE;
111
112        switch (\get_class($args->getObject())) {
113            // l'ajout d'un participant ou d'une conformité entraine la modification de l'évaluation
114            case Conformite::class:
115            case Participant::class:
116                $object = $object->getEvaluation();
117                $action = LogJournalActionDictionary::UPDATE;
118                break;
119                // l'ajout d'un contact IL ou d'un service entraine la modification de la structure
120            case ComiteIlContact::class:
121                $object = $object->getCollectivity();
122                $action = LogJournalActionDictionary::UPDATE;
123                break;
124                // l'ajout d'une réponse entraine la modification de la conformité du traitement
125            case Reponse::class:
126                $object = $object->getConformiteTraitement();
127                $action = LogJournalActionDictionary::UPDATE;
128                break;
129        }
130
131        $this->registerLog($object, $action);
132    }
133
134    public function postUpdate(LifecycleEventArgs $args): void
135    {
136        $object = $args->getObject();
137        if (!$this->supports($object)) {
138            return;
139        }
140
141        // specific case for user. Need to know which data is update
142        switch (\get_class($args->getObject())) {
143            case User::class:
144                $this->registerLogForUser($object);
145                break;
146            case Proof::class:
147            case Request::class:
148            case Violation::class:
149                $this->registerLogSoftDelete($object);
150                break;
151            case Conformite::class:
152            case Participant::class:
153                $this->registerLog($object->getEvaluation(), LogJournalActionDictionary::UPDATE);
154                break;
155            case ComiteIlContact::class:
156                $this->registerLog($object->getCollectivity(), LogJournalActionDictionary::UPDATE);
157                break;
158            case Reponse::class:
159                /** @var User $user */
160                $user       = $this->security->getUser();
161                $item       = $this->cacheAdapter->getItem('already_register_item-' . $user->getId()->toString());
162                $request    = $this->requestStack->getCurrentRequest();
163                $conformite = $object->getConformiteTraitement();
164                $id         = $request->get('id');
165                // Cas spéciale lors de l'édition du traitement lors de la modification d'une conformité d'un traitement
166                if (\is_null($item->get()) || $id === $item->get()->toString() && $conformite->getId()->toString() === $id) {
167                    $this->registerLog($conformite, LogJournalActionDictionary::UPDATE);
168                }
169                break;
170            default:
171                $this->registerLog($object, LogJournalActionDictionary::UPDATE);
172        }
173    }
174
175    public function preRemove(LifecycleEventArgs $args): void
176    {
177        if (!$this->supports($args->getObject())) {
178            return;
179        }
180
181        $this->registerDeleteLog($args);
182    }
183
184    private function registerLog(LoggableSubject $object, string $action)
185    {
186        /** @var User $user */
187        $user = $this->security->getUser();
188        $item = $this->cacheAdapter->getItem('already_register_item-' . $user->getId()->toString());
189        // si la clef de l'objet existe déja alors on ajoute pas de log.
190        if (!$item->isHit()) {
191            $item->expiresAfter(2);
192            $item->set($object->getId());
193            $this->cacheAdapter->save($item);
194        } elseif ($object->getId()->toString() === $item->get()->toString()) {
195            return;
196        }
197
198        $subject = LogJournalSubjectDictionary::getSubjectFromClassName(\get_class($object));
199
200        $log = new LogJournal(
201            $this->getCollectivity($object),
202            $user->getFullName(),
203            $user->getEmail(),
204            $action,
205            $subject,
206            $object->getId()->toString(),
207            $object->__toString()
208        );
209        $this->eventDispatcher->dispatch(new LogJournalEvent($log));
210    }
211
212    private function registerDeleteLog(LifecycleEventArgs $args)
213    {
214        $object = $args->getObject();
215
216        if ($this->notConcernedByDeletionLog($object)) {
217            return;
218        }
219
220        /** @var User $user */
221        $user = $this->security->getUser();
222
223        $subject = LogJournalSubjectDictionary::getSubjectFromClassName(\get_class($object));
224
225        $log = new LogJournal(
226            $this->getCollectivity($object),
227            $user->getFullName(),
228            $user->getEmail(),
229            LogJournalActionDictionary::DELETE,
230            $subject,
231            $object->getId()->toString(),
232            $object->__toString()
233        );
234
235        $this->eventDispatcher->dispatch(new LogJournalEvent($log, $object));
236    }
237
238    private function supports($object): bool
239    {
240        return $object instanceof LoggableSubject && !\is_null($this->security->getUser());
241    }
242
243    private function getCollectivity($object)
244    {
245        if (Collectivity::class === \get_class($object)) {
246            return $object;
247        }
248
249        if (\method_exists($object, 'getCollectivity')) {
250            return $object->getCollectivity();
251        }
252
253        /** @var User $user */
254        $user = $this->security->getUser();
255
256        return $user->getCollectivity();
257    }
258
259    private function registerLogForUser(LoggableSubject $object)
260    {
261        /** @var User $user */
262        $user    = $this->security->getUser();
263        $changes = $this->entityManager->getUnitOfWork()->getEntityChangeSet($object);
264
265        // don't need to add log on lastLogin because already register in LoginSubscriber
266        if (\array_key_exists('lastlogin', $changes)) {
267            return;
268        }
269
270        $collectivity = $this->getCollectivity($object);
271        if (\array_key_exists('deletedAt', $changes)) {
272            $action = LogJournalActionDictionary::SOFT_DELETE;
273            if (!\is_null($changes['deletedAt'][0])) {
274                $action = LogJournalActionDictionary::SOFT_DELETE_REVOKE;
275            }
276            $log = new LogJournal(
277                $collectivity,
278                $user->getFullName(),
279                $user->getEmail(),
280                $action,
281                LogJournalSubjectDictionary::USER_USER,
282                $object->getId()->toString(),
283                $object->__toString()
284            );
285            $this->eventDispatcher->dispatch(new LogJournalEvent($log));
286
287            return;
288        }
289
290        $subjectTypes = [];
291        if (\array_key_exists('firstName', $changes)) {
292            $subjectTypes[] = LogJournalSubjectDictionary::USER_FIRSTNAME;
293        }
294
295        if (\array_key_exists('lastName', $changes)) {
296            $subjectTypes[] = LogJournalSubjectDictionary::USER_LASTNAME;
297        }
298
299        if (\array_key_exists('email', $changes)) {
300            $subjectTypes[] = LogJournalSubjectDictionary::USER_EMAIL;
301        }
302
303        if (\array_key_exists('password', $changes)) {
304            $subjectTypes[] = LogJournalSubjectDictionary::USER_PASSWORD;
305        }
306
307        foreach ($subjectTypes as $subjectType) {
308            $log = new LogJournal(
309                $collectivity,
310                $user->getFullName(),
311                $user->getEmail(),
312                LogJournalActionDictionary::UPDATE,
313                $subjectType,
314                $object->getId()->toString(),
315                $object->__toString()
316            );
317            $this->eventDispatcher->dispatch(new LogJournalEvent($log));
318        }
319    }
320
321    private function registerLogSoftDelete(LoggableSubject $object)
322    {
323        /** @var User $user */
324        $user         = $this->security->getUser();
325        $changes      = $this->entityManager->getUnitOfWork()->getEntityChangeSet($object);
326        $collectivity = $this->getCollectivity($object);
327
328        // if is not a softdelete request, just register a log update
329        $action = LogJournalActionDictionary::UPDATE;
330        if (\array_key_exists('deletedAt', $changes)) {
331            $action = LogJournalActionDictionary::SOFT_DELETE;
332        }
333
334        $subject = LogJournalSubjectDictionary::getSubjectFromClassName(\get_class($object));
335        $log     = new LogJournal(
336            $collectivity,
337            $user->getFullName(),
338            $user->getEmail(),
339            $action,
340            $subject,
341            $object->getId()->toString(),
342            $object->__toString()
343        );
344        $this->eventDispatcher->dispatch(new LogJournalEvent($log));
345    }
346
347    private function notConcernedByDeletionLog(LoggableSubject $subject): bool
348    {
349        return \in_array(\get_class($subject), [
350            Conformite::class,
351            Participant::class,
352            ComiteIlContact::class,
353            Service::class,
354            Reponse::class,
355        ]);
356    }
357}