Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 219
0.00% covered (danger)
0.00%
0 / 20
CRAP
0.00% covered (danger)
0.00%
0 / 1
ModeleAnalyseController
0.00% covered (danger)
0.00%
0 / 219
0.00% covered (danger)
0.00%
0 / 20
3422
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getDomain
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getModel
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getModelClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFormType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 listAction
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 formPrePersistData
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
72
 createAction
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 editAction
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
20
 getQuestionsConformite
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 duplicateAction
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 rightsAction
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 listDataTables
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
72
 generateActioNCellContent
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
6
 getLabelAndKeysArray
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 exportAction
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
 importAction
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
56
 formatToFileCompliant
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 deleteConfirmationAction
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 ScenarioMenacesToDelete
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3declare(strict_types=1);
4
5namespace App\Domain\AIPD\Controller;
6
7use App\Application\Controller\CRUDController;
8use App\Application\Symfony\Security\UserProvider;
9use App\Application\Traits\ServersideDatatablesTrait;
10use App\Domain\AIPD\Dictionary\BaseCriterePrincipeFondamental;
11use App\Domain\AIPD\Form\Flow\ModeleAIPDFlow;
12use App\Domain\AIPD\Form\Type\ImportModeleType;
13use App\Domain\AIPD\Form\Type\ModeleAnalyseRightsType;
14use App\Domain\AIPD\Form\Type\ModeleAnalyseType;
15use App\Domain\AIPD\Model\ModeleAnalyse;
16use App\Domain\AIPD\Model\ModeleQuestionConformite;
17use App\Domain\AIPD\Model\ModeleScenarioMenace;
18use App\Domain\AIPD\Repository;
19use App\Domain\Registry\Repository\ConformiteTraitement\Question;
20use App\Domain\User\Repository\Collectivity;
21use App\Infrastructure\ORM\AIPD\Repository\ModeleMesureProtection as ModeleMesureProtectionRepository;
22use Doctrine\ORM\EntityManagerInterface;
23use Gaufrette\Exception\FileNotFound;
24use Gaufrette\FilesystemInterface;
25use JMS\Serializer\SerializerBuilder;
26use Knp\Snappy\Pdf;
27use Ramsey\Uuid\Uuid;
28use Symfony\Component\HttpFoundation\JsonResponse;
29use Symfony\Component\HttpFoundation\Request;
30use Symfony\Component\HttpFoundation\Response;
31use Symfony\Component\HttpFoundation\ResponseHeaderBag;
32use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
33use Symfony\Component\Routing\RouterInterface;
34use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
35use Symfony\Contracts\Translation\TranslatorInterface;
36use Symfony\Polyfill\Intl\Icu\Exception\MethodNotImplementedException;
37use Symfony\Polyfill\Intl\Icu\Exception\NotImplementedException;
38
39/**
40 * @property Repository\ModeleAnalyse $repository
41 */
42class ModeleAnalyseController extends CRUDController
43{
44    use ServersideDatatablesTrait;
45
46    /**
47     * @var Collectivity
48     */
49    protected $collectivityRepository;
50
51    private ModeleAIPDFlow $modeleFlow;
52    private Question $questionRepository;
53    private ModeleMesureProtectionRepository $mesureProtectionRepository;
54    private RouterInterface $router;
55    private FilesystemInterface $fichierFilesystem;
56
57    public function __construct(
58        EntityManagerInterface $entityManager,
59        TranslatorInterface $translator,
60        Repository\ModeleAnalyse $repository,
61        Pdf $pdf,
62        UserProvider $userProvider,
63        AuthorizationCheckerInterface $authorizationChecker,
64        Collectivity $collectivityRepository,
65        ModeleMesureProtectionRepository $mesureProtectionRepository,
66        ModeleAIPDFlow $modeleFlow,
67        Question $questionRepository,
68        RouterInterface $router,
69        FilesystemInterface $fichierFilesystem,
70    ) {
71        parent::__construct($entityManager, $translator, $repository, $pdf, $userProvider, $authorizationChecker);
72        $this->collectivityRepository     = $collectivityRepository;
73        $this->modeleFlow                 = $modeleFlow;
74        $this->questionRepository         = $questionRepository;
75        $this->mesureProtectionRepository = $mesureProtectionRepository;
76        $this->router                     = $router;
77        $this->fichierFilesystem          = $fichierFilesystem;
78    }
79
80    protected function getDomain(): string
81    {
82        return 'aipd';
83    }
84
85    protected function getModel(): string
86    {
87        return 'modele_analyse';
88    }
89
90    protected function getModelClass(): string
91    {
92        return ModeleAnalyse::class;
93    }
94
95    protected function getFormType(): string
96    {
97        return ModeleAnalyseType::class;
98    }
99
100    public function listAction(): Response
101    {
102        return $this->render('Aipd/Modele_analyse/list.html.twig', [
103            'totalItem' => $this->repository->count(),
104            'route'     => $this->router->generate('aipd_modele_analyse_list_datatables'),
105        ]);
106    }
107
108    /**
109     * {@inheritdoc}
110     * - Upload documentFile before object persistence in database.
111     *
112     * @throws \Exception
113     */
114    public function formPrePersistData($object, $form = null)
115    {
116        if (!$object instanceof ModeleAnalyse) {
117            throw new \RuntimeException('You must persist a ' . ModeleAnalyse::class . ' object class with your form');
118        }
119
120        foreach ($object->getCriterePrincipeFondamentaux() as $criterePrincipeFondamental) {
121            $deleteFile = $criterePrincipeFondamental->isDeleteFile();
122
123            if ($deleteFile) {
124                // Remove existing file
125                try {
126                    $this->fichierFilesystem->delete($criterePrincipeFondamental->getFichier());
127                } catch (FileNotFound $e) {
128                }
129
130                $criterePrincipeFondamental->setFichier(null);
131            }
132
133            $file = $criterePrincipeFondamental->getFichierFile();
134
135            if ($file) {
136                if (null !== $existing = $criterePrincipeFondamental->getFichier()) {
137                    try {
138                        $this->fichierFilesystem->delete($existing);
139                    } catch (FileNotFound $e) {
140                    }
141                }
142                $filename = Uuid::uuid4()->toString() . '.' . $file->getClientOriginalExtension();
143                $this->fichierFilesystem->write($filename, \fopen($file->getRealPath(), 'r'));
144                $criterePrincipeFondamental->setFichier($filename);
145                $criterePrincipeFondamental->setFichierFile(null);
146            }
147        }
148    }
149
150    public function createAction(Request $request): Response
151    {
152        $object = new ModeleAnalyse();
153        $object->setCriterePrincipeFondamentaux(BaseCriterePrincipeFondamental::getBaseCritere());
154        $object->setQuestionConformites($this->getQuestionsConformite($object));
155
156        $this->modeleFlow->bind($object);
157        $form = $this->modeleFlow->createForm();
158
159        if ($this->modeleFlow->isValid($form)) {
160            $this->formPrePersistData($object);
161            $this->modeleFlow->saveCurrentStepData($form);
162
163            if ($this->modeleFlow->nextStep()) {
164                $form = $this->modeleFlow->createForm();
165            } else {
166                $this->entityManager->persist($object);
167                $this->entityManager->flush();
168
169                $this->modeleFlow->reset();
170
171                $this->addFlash('success', $this->getFlashbagMessage('success', 'create', $object));
172
173                return $this->redirectToRoute($this->getRouteName('list'));
174            }
175        }
176
177        return $this->render($this->getTemplatingBasePath('create'), [
178            'form' => $form->createView(),
179            'flow' => $this->modeleFlow,
180        ]);
181    }
182
183    public function editAction(Request $request, string $id): Response
184    {
185        $object = $this->repository->findOneById($id);
186        if (!$object) {
187            throw new NotFoundHttpException("No object found with ID '{$id}'");
188        }
189
190        $this->modeleFlow->bind($object);
191        $form = $this->modeleFlow->createForm();
192
193        if ($this->modeleFlow->isValid($form)) {
194            $this->formPrePersistData($object);
195            $this->modeleFlow->saveCurrentStepData($form);
196
197            if ($this->modeleFlow->nextStep()) {
198                $form = $this->modeleFlow->createForm();
199            } else {
200                $this->ScenarioMenacesToDelete($object, $id);
201                $this->entityManager->persist($object);
202                $this->entityManager->flush();
203
204                $this->modeleFlow->reset();
205
206                $this->addFlash('success', $this->getFlashbagMessage('success', 'edit', $object));
207
208                return $this->redirectToRoute($this->getRouteName('list'));
209            }
210        }
211
212        return $this->render($this->getTemplatingBasePath('edit'), [
213            'form' => $form->createView(),
214            'flow' => $this->modeleFlow,
215        ]);
216    }
217
218    private function getQuestionsConformite(ModeleAnalyse $modeleAnalyse)
219    {
220        $questions = [];
221        foreach ($this->questionRepository->findAll(['position' => 'ASC']) as $question) {
222            $questions[] = new ModeleQuestionConformite($question->getQuestion(), $question->getPosition(), $modeleAnalyse);
223        }
224
225        return $questions;
226    }
227
228    public function duplicateAction(string $id): Response
229    {
230        throw new NotImplementedException('Not implemented yet');
231    }
232
233    public function rightsAction(Request $request, string $id): Response
234    {
235        $object = $this->repository->findOneById($id);
236        if (!$object) {
237            throw new NotFoundHttpException("No object found with ID '{$id}'");
238        }
239        $form = $this->createForm(ModeleAnalyseRightsType::class, $object);
240
241        $form->handleRequest($request);
242
243        if ($form->isSubmitted() && $form->isValid()) {
244            $this->formPrePersistData($object);
245            $this->entityManager->persist($object);
246            $this->entityManager->flush();
247
248            $this->addFlash('success', $this->getFlashbagMessage('success', 'rights', $object));
249
250            return $this->redirectToRoute($this->getRouteName('list'));
251        }
252
253        return $this->render('Aipd/Modele_analyse/rights.html.twig', [
254            'form' => $form->createView(),
255        ]);
256    }
257
258    public function listDataTables(Request $request): JsonResponse
259    {
260        $modeles = $this->getResults($request);
261        $reponse = $this->getBaseDataTablesResponse($request, $modeles);
262
263        foreach ($modeles as $modele) {
264            if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
265                $userCollectivity            = $this->userProvider->getAuthenticatedUser()->getCollectivity();
266                $userCollectivityType        = $userCollectivity->getType();
267                $authorizedCollectivities    = $modele->getAuthorizedCollectivities();
268                $authorizedCollectivityTypes = $modele->getAuthorizedCollectivityTypes();
269
270                if (!\is_null($authorizedCollectivityTypes)
271                && in_array($userCollectivityType, $authorizedCollectivityTypes)) {
272                    continue;
273                }
274
275                if ($authorizedCollectivities->contains($userCollectivity)) {
276                    continue;
277                }
278            }
279
280            $reponse['data'][] = [
281                'nom'         => $modele->getNom(),
282                'description' => $modele->getDescription(),
283                'createdAt'   => $modele->getCreatedAt() ? $modele->getCreatedAt()->format('d-m-Y H:i') : '',
284                'updatedAt'   => $modele->getUpdatedAt() ? $modele->getUpdatedAt()->format('d-m-Y H:i') : '',
285                'actions'     => $this->generateActioNCellContent($modele),
286            ];
287        }
288
289        $reponse['recordsTotal']    = count($reponse['data']);
290        $reponse['recordsFiltered'] = count($reponse['data']);
291
292        $jsonResponse = new JsonResponse($reponse);
293
294        return $jsonResponse;
295    }
296
297    private function generateActioNCellContent(ModeleAnalyse $modele)
298    {
299        $id                  = $modele->getId();
300        $htmltoReturnIfAdmin = '';
301
302        if ($this->authorizationChecker->isGranted('ROLE_ADMIN')) {
303            $htmltoReturnIfAdmin = '<a href="' . $this->router->generate('aipd_modele_analyse_rights', ['id' => $id]) . '">
304                <i aria-hidden="true" class="fa fa-user-shield"></i> '
305                . $this->translator->trans('global.action.rights') .
306            '</a>';
307        }
308
309        return
310            '<a href="' . $this->router->generate('aipd_modele_analyse_edit', ['id' => $id]) . '">
311                <i aria-hidden="true" class="fa fa-pencil"></i> '
312                . $this->translator->trans('global.action.edit') .
313            '</a>'
314            . $htmltoReturnIfAdmin .
315            '<a href="' . $this->router->generate('aipd_modele_analyse_export', ['id' => $id]) . '">
316                <i aria-hidden="true" class="fa fa-file-code"></i> ' .
317                $this->translator->trans('global.action.export') .
318            '</a>' .
319            '<a href="' . $this->router->generate('aipd_modele_analyse_delete', ['id' => $id]) . '">
320                <i aria-hidden="true" class="fa fa-trash"></i> ' .
321                $this->translator->trans('global.action.delete') .
322            '</a>';
323    }
324
325    protected function getLabelAndKeysArray(): array
326    {
327        return [
328            '0' => 'nom',
329            '1' => 'description',
330            '2' => 'createdAt',
331            '3' => 'updatedAt',
332            '4' => 'actions',
333        ];
334    }
335
336    public function exportAction(string $id)
337    {
338        $object = $this->repository->findOneById($id);
339        if (!$object) {
340            throw new NotFoundHttpException("No object found with ID '{$id}'");
341        }
342        /** @var ModeleAnalyse $toExport */
343        $toExport = clone $object;
344        $toExport->setCriterePrincipeFondamentaux($toExport->getCriterePrincipeFondamentaux()->toArray());
345
346        $serializer = SerializerBuilder::create()->build();
347        $xml        = $serializer->serialize($toExport, 'xml');
348
349        $response    = new Response($xml);
350        $disposition = $response->headers->makeDisposition(
351            ResponseHeaderBag::DISPOSITION_ATTACHMENT,
352            self::formatToFileCompliant($object->getNom()) . '.xml'
353        );
354
355        $response->headers->set('Content-Disposition', $disposition);
356
357        return $response;
358    }
359
360    public function importAction(Request $request)
361    {
362        $form = $this->createForm(ImportModeleType::class);
363        $form->handleRequest($request);
364        if ($form->isSubmitted() && $form->isValid()) {
365            $content = file_get_contents($form->getData()['file']->getPathname());
366            try {
367                $serializer = SerializerBuilder::create()->build();
368
369                // Replace all invalid dates with actual date
370                // Fixes https://gitlab.adullact.net/soluris/madis/-/issues/882
371                $content = str_replace('<created_at><![CDATA[-0001', '<created_at><![CDATA[' . date('Y'), $content);
372                $content = str_replace('<updated_at><![CDATA[-0001', '<updated_at><![CDATA[' . date('Y'), $content);
373                /** @var ModeleAnalyse $object */
374                $object = $serializer->deserialize($content, ModeleAnalyse::class, 'xml');
375                $object->deserialize();
376            } catch (\Exception $e) {
377                $this->addFlash('danger', "Impossible d'importer ce fichier : " . $e->getMessage());
378
379                return $this->redirectToRoute($this->getRouteName('list'));
380            }
381
382            $sm = [];
383            foreach ($object->getScenarioMenaces() as $scenarioMenace) {
384                /** @var ModeleScenarioMenace $scenarioMenace */
385                $mesures = [];
386                foreach ($scenarioMenace->getMesuresProtections() as $mesureProtection) {
387                    // Check if this mesure already exists
388                    $mm = $this->entityManager->find(\App\Domain\AIPD\Model\ModeleMesureProtection::class, $mesureProtection->getId());
389                    if ($mm) {
390                        $mesures[] = $mm;
391                    } else {
392                        // If not, save it now
393                        $this->entityManager->persist($mesureProtection);
394                        $mesures[] = $mesureProtection;
395                    }
396                }
397                $scenarioMenace->setMesuresProtections($mesures);
398                $sm[] = $scenarioMenace;
399            }
400
401            $object->setScenarioMenaces($sm);
402            $object->setCreatedAt(new \DateTimeImmutable());
403            $object->setNom('(import) ' . $object->getNom());
404            $this->entityManager->persist($object);
405            $this->entityManager->flush();
406            $this->addFlash('success', $this->getFlashbagMessage('success', 'import', $object));
407
408            return $this->redirectToRoute($this->getRouteName('list'));
409        }
410
411        return $this->render($this->getTemplatingBasePath('import'), [
412            'form' => $form->createView(),
413        ]);
414    }
415
416    private static function formatToFileCompliant(string $string)
417    {
418        $unwanted_array = [
419            'Š' => 'S', 'š' => 's', 'Ž' => 'Z', 'ž' => 'z', 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'A', 'Ç' => 'C', 'È' => 'E', 'É' => 'E',
420            'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ø' => 'O', 'Ù' => 'U',
421            'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ý' => 'Y', 'Þ' => 'B', 'ß' => 'Ss', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'a', 'ç' => 'c',
422            'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ð' => 'o', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o',
423            'ö' => 'o', 'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ý' => 'y', 'þ' => 'b', 'ÿ' => 'y',
424        ];
425
426        return strtr($string, $unwanted_array);
427    }
428
429    /**
430     * The deletion action
431     * Delete the data.
432     *
433     * @throws \Exception
434     */
435    public function deleteConfirmationAction(string $id): Response
436    {
437        $object = $this->repository->findOneById($id);
438        if (!$object) {
439            throw new NotFoundHttpException("No object found with ID '{$id}'");
440        }
441
442        if ($this->isSoftDelete()) {
443            if (!\method_exists($object, 'setDeletedAt')) {
444                throw new MethodNotImplementedException('setDeletedAt');
445            }
446            $object->setDeletedAt(new \DateTimeImmutable());
447            $this->repository->update($object);
448        } else {
449            foreach ($this->mesureProtectionRepository->findToDelete($object) as $measureToDelete) {
450                $this->entityManager->remove($measureToDelete);
451            }
452            $this->entityManager->remove($object);
453
454            $this->entityManager->flush();
455        }
456
457        $this->addFlash('success', $this->getFlashbagMessage('success', 'delete', $object));
458
459        return $this->redirectToRoute($this->getRouteName('list'));
460    }
461
462    private function ScenarioMenacesToDelete($object, $modeleAnalyseId)
463    {
464        $ScenarioMenacesToDelete = [];
465        $scenarioMenacesActual   = $this->entityManager->getRepository(ModeleScenarioMenace::class)->findBy(['modeleAnalyse' => $modeleAnalyseId]);
466
467        foreach ($scenarioMenacesActual as $actualScenarioMenace) {
468            if (!in_array($actualScenarioMenace, $object->getScenarioMenaces()->toArray())) {
469                $ScenarioMenacesToDelete[] = $actualScenarioMenace;
470            }
471        }
472
473        foreach ($ScenarioMenacesToDelete as $menaceToDelete) {
474            /** @var ModeleScenarioMenace $menace */
475            $menace = $this->entityManager->getRepository(ModeleScenarioMenace::class)->find($menaceToDelete->getId());
476            $this->entityManager->remove((object) $menace);
477        }
478    }
479}