Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
5.45% covered (danger)
5.45%
11 / 202
27.78% covered (danger)
27.78%
5 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
ProofController
5.45% covered (danger)
5.45%
11 / 202
27.78% covered (danger)
27.78%
5 / 18
2519.09
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getDomain
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getModel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getModelClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFormType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formPrePersistData
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 archiveAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 archiveConfirmationAction
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 unarchiveAction
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 unarchiveConfirmationAction
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 deleteConfirmationAction
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
 downloadAction
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
 downloadAll
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
110
 listAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 listDataTables
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
 getLabelAndKeysArray
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
12
 getRequestCriteria
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 getActionCellsContent
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
30
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\Registry\Controller;
25
26use App\Application\Controller\CRUDController;
27use App\Application\Symfony\Security\UserProvider;
28use App\Application\Traits\ServersideDatatablesTrait;
29use App\Domain\Documentation\Model\Category;
30use App\Domain\Registry\Dictionary\ProofTypeDictionary;
31use App\Domain\Registry\Form\Type\ProofType;
32use App\Domain\Registry\Model;
33use App\Domain\Registry\Repository;
34use App\Domain\Reporting\Handler\WordHandler;
35use App\Domain\Tools\ChainManipulator;
36use App\Domain\User\Dictionary\UserRoleDictionary;
37use App\Domain\User\Model\User;
38use Doctrine\ORM\EntityManagerInterface;
39use Gaufrette\FilesystemInterface;
40use Knp\Snappy\Pdf;
41use PhpOffice\PhpWord\Shared\ZipArchive;
42use Ramsey\Uuid\Uuid;
43use Symfony\Component\HttpFoundation\BinaryFileResponse;
44use Symfony\Component\HttpFoundation\JsonResponse;
45use Symfony\Component\HttpFoundation\Request;
46use Symfony\Component\HttpFoundation\RequestStack;
47use Symfony\Component\HttpFoundation\Response;
48use Symfony\Component\HttpFoundation\ResponseHeaderBag;
49use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
50use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
51use Symfony\Component\Routing\RouterInterface;
52use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
53use Symfony\Contracts\Translation\TranslatorInterface;
54use Symfony\Polyfill\Intl\Icu\Exception\MethodNotImplementedException;
55
56/**
57 * @property Repository\Proof $repository
58 */
59class ProofController extends CRUDController
60{
61    use ServersideDatatablesTrait;
62
63    /**
64     * @var RequestStack
65     */
66    protected $requestStack;
67
68    /**
69     * @var WordHandler
70     */
71    protected $wordHandler;
72
73    /**
74     * @var AuthorizationCheckerInterface
75     */
76    protected $authorizationChecker;
77
78    /**
79     * @var UserProvider
80     */
81    protected $userProvider;
82
83    /**
84     * @var FilesystemInterface
85     */
86    protected $documentFilesystem;
87
88    /**
89     * @var RouterInterface
90     */
91    protected $router;
92
93    public function __construct(
94        EntityManagerInterface $entityManager,
95        TranslatorInterface $translator,
96        Repository\Proof $repository,
97        RequestStack $requestStack,
98        WordHandler $wordHandler,
99        AuthorizationCheckerInterface $authorizationChecker,
100        UserProvider $userProvider,
101        FilesystemInterface $documentFilesystem,
102        Pdf $pdf,
103        RouterInterface $router,
104    ) {
105        parent::__construct($entityManager, $translator, $repository, $pdf, $userProvider, $authorizationChecker);
106        $this->requestStack         = $requestStack;
107        $this->wordHandler          = $wordHandler;
108        $this->authorizationChecker = $authorizationChecker;
109        $this->userProvider         = $userProvider;
110        $this->documentFilesystem   = $documentFilesystem;
111        $this->router               = $router;
112    }
113
114    protected function getDomain(): string
115    {
116        return 'registry';
117    }
118
119    protected function getModel(): string
120    {
121        return 'proof';
122    }
123
124    protected function getModelClass(): string
125    {
126        return Model\Proof::class;
127    }
128
129    protected function getFormType(): string
130    {
131        return ProofType::class;
132    }
133
134    /**
135     * {@inheritdoc}
136     * - Upload documentFile before object persistence in database.
137     *
138     * @throws \Exception
139     */
140    public function formPrePersistData($object, $form = null)
141    {
142        if (!$object instanceof Model\Proof) {
143            throw new \RuntimeException('You must persist a ' . Model\Proof::class . ' object class with your form');
144        }
145
146        $file = $object->getDocumentFile();
147
148        if ($file) {
149            $filename = Uuid::uuid4()->toString() . '.' . $file->getClientOriginalExtension();
150            $this->documentFilesystem->write($filename, \fopen($file->getRealPath(), 'r'));
151            $object->setDocument($filename);
152            $object->setDocumentFile(null);
153        }
154    }
155
156    /**
157     * The archive action view
158     * Display a confirmation message to confirm data archivage.
159     */
160    public function archiveAction(string $id): Response
161    {
162        /** @var Model\Proof $object */
163        $object = $this->repository->findOneById($id);
164        if (!$object) {
165            throw new NotFoundHttpException("No object found with ID '{$id}'");
166        }
167
168        /** @var User $user */
169        $user = $this->getUser();
170        if (!$user->hasAccessTo($object)) {
171            return $this->redirectToRoute($this->getRouteName('list'));
172        }
173
174        return $this->render($this->getTemplatingBasePath('archive'), [
175            'object' => $object,
176        ]);
177    }
178
179    /**
180     * The archive action
181     * Archive the data.
182     *
183     * @throws \Exception
184     */
185    public function archiveConfirmationAction(string $id): Response
186    {
187        /** @var Model\Proof|null $object */
188        $object = $this->repository->findOneById($id);
189        if (!$object) {
190            throw new NotFoundHttpException("No object found with ID '{$id}'");
191        }
192
193        /** @var User $user */
194        $user = $this->getUser();
195        if (!$user->hasAccessTo($object)) {
196            return $this->redirectToRoute($this->getRouteName('list'));
197        }
198
199        $object->setDeletedAt(new \DateTimeImmutable());
200        $this->repository->update($object);
201
202        $this->addFlash('success', $this->getFlashbagMessage('success', 'archive', $object));
203
204        return $this->redirectToRoute($this->getRouteName('list'));
205    }
206
207    /**
208     * The archive action view
209     * Display a confirmation message to confirm data archivage.
210     */
211    public function unarchiveAction(string $id): Response
212    {
213        $object = $this->repository->findOneById($id);
214        if (!$object) {
215            throw new NotFoundHttpException("No object found with ID '{$id}'");
216        }
217
218        return $this->render($this->getTemplatingBasePath('unarchive'), [
219            'object' => $object,
220        ]);
221    }
222
223    /**
224     * The archive action
225     * Archive the data.
226     *
227     * @throws \Exception
228     */
229    public function unarchiveConfirmationAction(string $id): Response
230    {
231        /** @var Model\Proof|null $object */
232        $object = $this->repository->findOneById($id);
233        if (!$object) {
234            throw new NotFoundHttpException("No object found with ID '{$id}'");
235        }
236
237        $object->setDeletedAt(null);
238        $this->repository->update($object);
239
240        $this->addFlash('success', $this->getFlashbagMessage('success', 'unarchive', $object));
241
242        return $this->redirectToRoute($this->getRouteName('list'));
243    }
244
245    /**
246     * {@inheritdoc}
247     * OVERRIDE METHOD.
248     * Override deletion in order to delete Proof into server.
249     */
250    public function deleteConfirmationAction(string $id): Response
251    {
252        /** @var Model\Proof|null $object */
253        $object = $this->repository->findOneById($id);
254        if (!$object) {
255            throw new NotFoundHttpException("No object found with ID '{$id}'");
256        }
257
258        /** @var User $user */
259        $user = $this->getUser();
260        if (!$user->hasAccessTo($object)) {
261            return $this->redirectToRoute($this->getRouteName('list'));
262        }
263
264        if ($this->isSoftDelete()) {
265            if (!\method_exists($object, 'setDeletedAt')) {
266                throw new MethodNotImplementedException('setDeletedAt');
267            }
268            $object->setDeletedAt(new \DateTimeImmutable());
269            $this->repository->update($object);
270        } else {
271            $filename = $object->getDocument();
272
273            $this->entityManager->remove($object);
274            $this->entityManager->flush();
275
276            // TODO: Log error if deletion fail
277            $this->documentFilesystem->delete($filename);
278        }
279
280        $this->addFlash('success', $this->getFlashbagMessage('success', 'delete', $object));
281
282        return $this->redirectToRoute($this->getRouteName('list'));
283    }
284
285    /**
286     * Download uploaded document which belongs to provided object id.
287     *
288     * @throws \Exception
289     */
290    public function downloadAction(string $id): Response
291    {
292        /** @var Model\Proof|null $object */
293        $object = $this->repository->findOneById($id);
294
295        if (!$object) {
296            throw new NotFoundHttpException("No object exists with id '{$id}'");
297        }
298        /** @var User $user */
299        $user = $this->getUser();
300
301        // Can only download if we belong to the collectivity or if we are admin
302        if (!$user->hasAccessTo($object, false)) {
303            throw new AccessDeniedHttpException('Vous ne pouvez pas télécharger cette preuve');
304        }
305
306        $extension = \pathinfo($object->getDocument(), PATHINFO_EXTENSION);
307        $response  = new BinaryFileResponse('gaufrette://registry_proof_document/' . $object->getDocument());
308
309        $filenameWithoutAccent = ChainManipulator::removeAccents($object->getName());
310        $filename              = ChainManipulator::removeAllNonAlphaNumericChar($filenameWithoutAccent);
311
312        $response->setContentDisposition(
313            ResponseHeaderBag::DISPOSITION_ATTACHMENT,
314            "{$filename}.{$extension}"
315        );
316
317        return $response;
318    }
319
320    public function downloadAll()
321    {
322        $objects = [];
323        if ('ROLE_ADMIN' === $this->userProvider->getAuthenticatedUser()->getRoles()[0]) {
324            $objects = $this->repository->findAll();
325        }
326
327        if ('ROLE_REFERENT' === $this->userProvider->getAuthenticatedUser()->getRoles()[0]) {
328            $collectivities = \iterable_to_array($this->userProvider->getAuthenticatedUser()->getCollectivitesReferees());
329
330            foreach ($collectivities as $collectivity) {
331                $objects = array_merge($objects, $this->repository->findAllByCollectivity($collectivity));
332            }
333        }
334
335        if (('ROLE_USER' == $this->userProvider->getAuthenticatedUser()->getRoles()[0]) || ('ROLE_PREVIEW' == $this->userProvider->getAuthenticatedUser()->getRoles()[0])) {
336            $collectivity = $this->userProvider->getAuthenticatedUser()->getCollectivity();
337            $objects      = $this->repository->findAllByCollectivity($collectivity);
338        }
339
340        $files = [];
341        foreach ($objects as $object) {
342            /** @var Model\Proof|null $object */
343            if (!$object->getDeletedAt()) {
344                $fileName = str_replace(' ', '_', $object->getName()) . '-' . $object->getDocument();
345                $files[]  = [$object->getDocument(), $fileName];
346            }
347        }
348        $zip = new ZipArchive();
349
350        $dir = $this->getParameter('kernel.project_dir') . '/public/uploads/registry/proof/zip/';
351
352        if (!is_dir($dir)) {
353            mkdir($dir, 0777, true);
354        }
355
356        $filename = $dir . 'test.zip';
357
358        $zip->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE);
359
360        foreach ($files as $file) {
361            $zip->addFile('./uploads/registry/proof/document/' . $file[0], $file[1]);
362        }
363
364        $zip->close();
365
366        $date     = date('dmY');
367        $response = new Response(file_get_contents($filename));
368        $response->headers->set('Content-Type', 'application/zip');
369        $response->headers->set('Content-Disposition', 'attachment;filename="Documents' . $date . '.zip"');
370        $response->headers->set('Content-length', filesize($filename));
371
372        return $response;
373    }
374
375    public function listAction(): Response
376    {
377        $criteria = $this->getRequestCriteria();
378
379        $category = $this->entityManager->getRepository(Category::class)->findOneBy([
380            'name' => 'Preuves',
381        ]);
382
383        return $this->render($this->getTemplatingBasePath('list'), [
384            'totalItem' => $this->repository->count($criteria),
385            'category'  => $category,
386            'route'     => $this->router->generate('registry_proof_list_datatables', ['archive' => $criteria['archive']]),
387        ]);
388    }
389
390    public function listDataTables(Request $request): JsonResponse
391    {
392        $criteria = $this->getRequestCriteria();
393        $users    = $this->getResults($request, $criteria);
394        $reponse  = $this->getBaseDataTablesResponse($request, $users, $criteria);
395
396        /** @var Model\Proof $proof */
397        foreach ($users as $proof) {
398            $reponse['data'][] = [
399                'nom'          => $proof->getName(),
400                'collectivite' => $this->isGranted('ROLE_REFERENT') && $proof->getCollectivity() ? $proof->getCollectivity()->getName() : '',
401                'service'      => $proof->getService() ? $proof->getService()->getName() : '',
402                'type'         => !\is_null($proof->getType()) ? ProofTypeDictionary::getTypes()[$proof->getType()] : null,
403                'commentaire'  => $proof->getComment(),
404                'date'         => \date_format($proof->getCreatedAt(), 'd/m/Y H:i'),
405                'updatedAt'    => \date_format($proof->getUpdatedAt(), 'd/m/Y H:i'),
406                'actions'      => $this->getActionCellsContent($proof),
407            ];
408        }
409
410        $jsonResponse = new JsonResponse();
411        $jsonResponse->setJson(\json_encode($reponse));
412
413        return $jsonResponse;
414    }
415
416    protected function getLabelAndKeysArray(): array
417    {
418        if ($this->isGranted('ROLE_REFERENT')) {
419            return [
420                'nom',
421                'collectivite',
422                'service',
423                'type',
424                'commentaire',
425                'date',
426                'updatedAt',
427                'actions',
428            ];
429        }
430        if ($this->userProvider->getAuthenticatedUser()->hasServices()) {
431            return [
432                'nom',
433                'service',
434                'type',
435                'commentaire',
436                'date',
437                'updatedAt',
438                'actions',
439            ];
440        }
441
442        return [
443            'nom',
444            'type',
445            'commentaire',
446            'date',
447            'updatedAt',
448            'actions',
449        ];
450    }
451
452    private function getRequestCriteria()
453    {
454        $criteria            = [];
455        $request             = $this->requestStack->getMasterRequest();
456        $criteria['archive'] = $request->query->getBoolean('archive');
457        $user                = $this->userProvider->getAuthenticatedUser();
458
459        if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
460            $criteria['collectivity'] = $user->getCollectivity();
461        }
462
463        if (\in_array(UserRoleDictionary::ROLE_REFERENT, $user->getRoles())) {
464            $criteria['collectivity'] = $user->getCollectivitesReferees();
465        }
466
467        return $criteria;
468    }
469
470    private function getActionCellsContent(Model\Proof $proof)
471    {
472        $cellContent = '';
473        $user        = $this->userProvider->getAuthenticatedUser();
474        if ($this->authorizationChecker->isGranted('ROLE_PREVIEW')) {
475            $cellContent .= '<a href="' . $this->router->generate('registry_proof_download', ['id' => $proof->getId()]) . '">
476                <i aria-hidden="true" class="fa fa-download"></i> ' .
477                $this->translator->trans('global.action.download') . '
478            </a>';
479        }
480
481        if ($this->authorizationChecker->isGranted('ROLE_USER') && $user->hasAccessTo($proof)) {
482            if (\is_null($proof->getDeletedAt())) {
483                $cellContent .= '<a href="' . $this->router->generate('registry_proof_edit', ['id' => $proof->getId()]) . '">
484                    <i aria-hidden="true" class="fa fa-pencil"></i> ' .
485                        $this->translator->trans('global.action.edit') . '
486                </a>
487                <a href="' . $this->router->generate('registry_proof_archive', ['id' => $proof->getId()]) . '">
488                    <i aria-hidden="true" class="fa fa-archive"></i> ' .
489                    $this->translator->trans('global.action.archive') . '
490                </a>';
491            } else {
492                $cellContent .= '<a href="' . $this->router->generate('registry_proof_unarchive', ['id' => $proof->getId()]) . '">
493                    <i aria-hidden="true" class="fa fa-archive"></i> ' .
494                    $this->translator->trans('global.action.unarchive') . '
495                </a>';
496            }
497            $cellContent .= '<a href="' . $this->router->generate('registry_proof_delete', ['id' => $proof->getId()]) . '">
498                <i aria-hidden="true" class="fa fa-trash"></i> ' .
499                $this->translator->trans('global.action.delete') . '
500            </a>';
501        }
502
503        return $cellContent;
504    }
505}