Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
14.72% covered (danger)
14.72%
39 / 265
36.84% covered (danger)
36.84%
7 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
MesurementController
14.72% covered (danger)
14.72%
39 / 265
36.84% covered (danger)
36.84%
7 / 19
1862.74
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
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
 getListData
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 listAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 reportAction
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 reportActionPlan
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 actionPlanAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 apiGetMesurementsByCollectivity
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
 createFromJsonAction
75.00% covered (warning)
75.00%
12 / 16
0.00% covered (danger)
0.00%
0 / 1
4.25
 showMesurementAction
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
72
 getRequestCriteria
70.00% covered (warning)
70.00%
7 / 10
0.00% covered (danger)
0.00%
0 / 1
4.43
 getLabelAndKeysArray
0.00% covered (danger)
0.00%
0 / 91
0.00% covered (danger)
0.00%
0 / 1
42
 listDataTables
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
56
 generateShowLink
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 generateActionCell
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 deleteConfirmationAction
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
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\Interfaces\CollectivityRelated;
28use App\Application\Symfony\Security\UserProvider;
29use App\Application\Traits\ServersideDatatablesTrait;
30use App\Domain\Documentation\Model\Category;
31use App\Domain\Registry\Dictionary\MesurementPriorityDictionary;
32use App\Domain\Registry\Dictionary\MesurementStatusDictionary;
33use App\Domain\Registry\Form\Type\MesurementType;
34use App\Domain\Registry\Model;
35use App\Domain\Registry\Repository;
36use App\Domain\Reporting\Handler\WordHandler;
37use App\Domain\User\Dictionary\UserRoleDictionary;
38use App\Domain\User\Model as UserModel;
39use App\Domain\User\Model\User;
40use App\Domain\User\Repository as UserRepository;
41use Doctrine\ORM\EntityManagerInterface;
42use Knp\Snappy\Pdf;
43use Symfony\Component\Form\FormFactoryInterface;
44use Symfony\Component\HttpFoundation\JsonResponse;
45use Symfony\Component\HttpFoundation\Request;
46use Symfony\Component\HttpFoundation\RequestStack;
47use Symfony\Component\HttpFoundation\Response;
48use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
49use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
50use Symfony\Component\Routing\RouterInterface;
51use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
52use Symfony\Contracts\Translation\TranslatorInterface;
53use Symfony\Polyfill\Intl\Icu\Exception\MethodNotImplementedException;
54
55/**
56 * @property Repository\Mesurement $repository
57 */
58class MesurementController extends CRUDController
59{
60    use ServersideDatatablesTrait;
61
62    /**
63     * @var UserRepository\Collectivity
64     */
65    protected $collectivityRepository;
66
67    /**
68     * @var WordHandler
69     */
70    protected $wordHandler;
71
72    /**
73     * @var AuthorizationCheckerInterface
74     */
75    protected $authorizationChecker;
76
77    /**
78     * @var UserProvider
79     */
80    protected $userProvider;
81
82    /**
83     * @var FormFactoryInterface
84     */
85    protected $formFactory;
86
87    /**
88     * @var RouterInterface
89     */
90    protected $router;
91
92    /**
93     * @var RequestStack
94     */
95    private $requestStack;
96
97    public function __construct(
98        EntityManagerInterface $entityManager,
99        TranslatorInterface $translator,
100        Repository\Mesurement $repository,
101        UserRepository\Collectivity $collectivityRepository,
102        WordHandler $wordHandler,
103        AuthorizationCheckerInterface $authorizationChecker,
104        UserProvider $userProvider,
105        FormFactoryInterface $formFactory,
106        RouterInterface $router,
107        Pdf $pdf,
108        RequestStack $requestStack,
109    ) {
110        parent::__construct($entityManager, $translator, $repository, $pdf, $userProvider, $authorizationChecker);
111        $this->collectivityRepository = $collectivityRepository;
112        $this->wordHandler            = $wordHandler;
113        $this->authorizationChecker   = $authorizationChecker;
114        $this->userProvider           = $userProvider;
115        $this->formFactory            = $formFactory;
116        $this->router                 = $router;
117        $this->requestStack           = $requestStack;
118    }
119
120    protected function getDomain(): string
121    {
122        return 'registry';
123    }
124
125    protected function getModel(): string
126    {
127        return 'mesurement';
128    }
129
130    protected function getModelClass(): string
131    {
132        return Model\Mesurement::class;
133    }
134
135    protected function getFormType(): string
136    {
137        return MesurementType::class;
138    }
139
140    protected function getListData()
141    {
142        $request  = $this->requestStack->getCurrentRequest();
143        $criteria = $this->getRequestCriteria($request);
144
145        return $this->repository->findBy($criteria);
146    }
147
148    public function listAction(): Response
149    {
150        $request = $this->requestStack->getCurrentRequest();
151
152        $category = $this->entityManager->getRepository(Category::class)->findOneBy([
153            'name' => 'Action de protection',
154        ]);
155
156        return $this->render($this->getTemplatingBasePath('list'), [
157            'totalItem' => $this->repository->count($this->getRequestCriteria($request)),
158            'category'  => $category,
159            'route'     => $this->router->generate('registry_mesurement_list_datatables'),
160        ]);
161    }
162
163    /**
164     * Generate a word report of contractors.
165     *
166     * @throws \PhpOffice\PhpWord\Exception\Exception
167     */
168    public function reportAction(): Response
169    {
170        $objects = $this->repository->findAllByCollectivity(
171            $this->userProvider->getAuthenticatedUser()->getCollectivity(),
172            ['name' => 'asc']
173        );
174
175        return $this->wordHandler->generateRegistryMesurementReport($objects);
176    }
177
178    /**
179     * Generate a word report of contractors.
180     *
181     * @throws \PhpOffice\PhpWord\Exception\Exception
182     */
183    public function reportActionPlan(): Response
184    {
185        $objects = $this->repository->findAllByCollectivity(
186            $this->userProvider->getAuthenticatedUser()->getCollectivity(),
187            ['name' => 'asc']
188        );
189
190        $filteredObjects = array_filter($objects, function ($object) {
191            return null !== $object->getPlanificationDate() && 'not-applicable' !== $object->getStatus();
192        });
193
194        return $this->wordHandler->generateRegistryMesurementReport($filteredObjects, true);
195    }
196
197    /**
198     * Display list of action plan
199     * Action plan are mesurement planified which are not yet applied.
200     *
201     * @return Response
202     */
203    public function actionPlanAction()
204    {
205        $request = $this->requestStack->getCurrentRequest();
206
207        $category = $this->entityManager->getRepository(Category::class)->findOneBy([
208            'name' => "Plan d'action",
209        ]);
210
211        return $this->render('Registry/Mesurement/action_plan.html.twig', [
212            'totalItem' => $this->repository->count($this->getRequestCriteria($request)),
213            'category'  => $category,
214            'route'     => $this->router->generate('registry_mesurement_list_datatables', ['action_plan' => true]),
215        ]);
216    }
217
218    /**
219     * Get all active treatments of a collectivity and return their id/name as JSON.
220     */
221    public function apiGetMesurementsByCollectivity(string $collectivityId): Response
222    {
223        if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
224            throw new AccessDeniedHttpException('You can\'t access to a collectivity mesurement data');
225        }
226
227        /** @var UserModel\Collectivity|null $collectivity */
228        $collectivity = $this->collectivityRepository->findOneById($collectivityId);
229        if (null === $collectivity) {
230            throw new NotFoundHttpException('Can\'t find collectivity for id ' . $collectivityId);
231        }
232
233        $mesurements = $this->repository->findAllByCollectivity(
234            $collectivity,
235            [
236                'name' => 'ASC',
237            ]
238        );
239        $responseData = [];
240
241        /** @var Model\Mesurement $mesurement */
242        foreach ($mesurements as $mesurement) {
243            $responseData[] = [
244                'value' => $mesurement->getId()->toString(),
245                'text'  => $mesurement->__toString(),
246            ];
247        }
248
249        return new JsonResponse($responseData);
250    }
251
252    /**
253     * Route to create an not applied mesurement with the modal.
254     *
255     * @return JsonResponse
256     */
257    public function createFromJsonAction(Request $request)
258    {
259        $form = $this->formFactory->create($this->getFormType(), null, ['csrf_protection' => false]);
260        $form->handleRequest($request);
261
262        if (!$form->isSubmitted() || !$form->isValid()) {
263            $errors = [];
264            foreach ($form->getErrors(true) as $error) {
265                $errors[$error->getOrigin()->getName()] = $error->getMessage();
266            }
267
268            return new JsonResponse($errors, Response::HTTP_BAD_REQUEST);
269        }
270
271        /** @var Model\Mesurement $object */
272        $object = $form->getData();
273        $object->setStatus(MesurementStatusDictionary::STATUS_NOT_APPLIED);
274
275        $this->entityManager->persist($object);
276        $this->entityManager->flush();
277
278        $dataToSerialize = [
279            'id'   => $object->getId()->toString(),
280            'name' => $object->getName(),
281        ];
282
283        return new JsonResponse($dataToSerialize, Response::HTTP_CREATED);
284    }
285
286    public function showMesurementAction(Request $request, string $id): Response
287    {
288        /* We get the referer to know if we come from MesurementListe or from PlanActionListe to return to the corresponding list */
289        $referer = filter_var($request->headers->get('referer'), FILTER_SANITIZE_URL);
290        if (null === $referer) {
291            $this->router->generate('registry_mesurement_list');
292        }
293
294        $object = $this->repository->findOneById($id);
295        if (!$object) {
296            throw new NotFoundHttpException("No object found with ID '{$id}'");
297        }
298        /** @var User $user */
299        $user = $this->getUser();
300        if (!$user->hasAccessTo($object, false)) {
301            return $this->redirectToRoute($this->getRouteName('list'));
302        }
303
304        $actionEnabled = true;
305
306        if ($object instanceof CollectivityRelated && !$this->authorizationChecker->isGranted('ROLE_ADMIN') && !$user->getServices()->isEmpty()) {
307            $actionEnabled = $object->isInUserServices($this->userProvider->getAuthenticatedUser());
308        }
309
310        if (!$this->isGranted('ROLE_USER')) {
311            $actionEnabled = false;
312        }
313
314        return $this->render($this->getTemplatingBasePath('show'), [
315            'object'        => $object,
316            'referer'       => $referer,
317            'actionEnabled' => $actionEnabled,
318        ]);
319    }
320
321    private function getRequestCriteria(Request $request)
322    {
323        $criteria = [];
324        $user     = $this->userProvider->getAuthenticatedUser();
325
326        if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
327            $criteria['collectivity'] = $user->getCollectivity();
328        }
329
330        if (\in_array(UserRoleDictionary::ROLE_REFERENT, $user->getRoles())) {
331            $criteria['collectivity'] = $user->getCollectivitesReferees();
332        }
333
334        if ($request->query->getBoolean('action_plan')) {
335            // Since we have to display planified & not-applied mesurement, filter
336            $criteria['planificationDate'] = 'null';
337            $criteria['status']            = MesurementStatusDictionary::STATUS_NOT_APPLIED;
338        }
339
340        return $criteria;
341    }
342
343    protected function getLabelAndKeysArray(): array
344    {
345        $request      = $this->requestStack->getCurrentRequest();
346        $isActionPlan = $request->query->getBoolean('action_plan');
347
348        if ($isActionPlan) {
349            if ($this->isGranted('ROLE_REFERENT')) {
350                return [
351                    'nom',
352                    'collectivite',
353                    'service',
354                    'date_planification',
355                    'cout',
356                    'charge',
357                    'priorite',
358                    'responsable_action',
359                    'description',
360                    'observation',
361                    'createdAt',
362                    'updatedAt',
363                    'actions',
364                ];
365            } elseif ($this->userProvider->getAuthenticatedUser()->hasServices()) {
366                return [
367                    'nom',
368                    'service',
369                    'date_planification',
370                    'cout',
371                    'charge',
372                    'priorite',
373                    'responsable_action',
374                    'description',
375                    'observation',
376                    'createdAt',
377                    'updatedAt',
378                    'actions',
379                ];
380            }
381
382            return [
383                'nom',
384                'date_planification',
385                'cout',
386                'charge',
387                'priorite',
388                'responsable_action',
389                'description',
390                'observation',
391                'createdAt',
392                'updatedAt',
393                'actions',
394            ];
395        }
396        if ($this->isGranted('ROLE_REFERENT')) {
397            return [
398                'nom',
399                'collectivite',
400                'service',
401                'statut',
402                'cout',
403                'charge',
404                'priorite',
405                'responsable_action',
406                'description',
407                'observation',
408                'createdAt',
409                'updatedAt',
410                'actions',
411            ];
412        } elseif ($this->userProvider->getAuthenticatedUser()->hasServices()) {
413            return [
414                'nom',
415                'service',
416                'statut',
417                'cout',
418                'charge',
419                'priorite',
420                'responsable_action',
421                'description',
422                'observation',
423                'createdAt',
424                'updatedAt',
425                'actions',
426            ];
427        }
428
429        return [
430            'nom',
431            'statut',
432            'cout',
433            'charge',
434            'priorite',
435            'responsable_action',
436            'description',
437            'observation',
438            'createdAt',
439            'updatedAt',
440            'actions',
441        ];
442    }
443
444    public function listDataTables(Request $request): JsonResponse
445    {
446        $criteria = $this->getRequestCriteria($request);
447        $actions  = $this->getResults($request, $criteria);
448        $reponse  = $this->getBaseDataTablesResponse($request, $actions, $criteria);
449
450        $request      = $this->requestStack->getCurrentRequest();
451        $isActionPlan = $request->query->getBoolean('action_plan');
452
453        /** @var Model\Mesurement $action */
454        foreach ($actions as $action) {
455            $reponse['data'][] = [
456                'id'                 => $action->getId(),
457                'nom'                => $this->generateShowLink($action),
458                'collectivite'       => $this->authorizationChecker->isGranted('ROLE_REFERENT') && $action->getCollectivity() ? $action->getCollectivity()->getName() : '',
459                'service'            => $action->getService() ? $action->getService()->getName() : '',
460                'statut'             => MesurementStatusDictionary::getStatus()[$action->getStatus()],
461                'cout'               => $action->getCost(),
462                'charge'             => $action->getCharge(),
463                'priorite'           => !\is_null($action->getPriority()) ? MesurementPriorityDictionary::getPriorities()[$action->getPriority()] : null,
464                'date_planification' => !\is_null($action->getPlanificationDate()) ? \date_format($action->getPlanificationDate(), 'd/m/Y') : null,
465                'responsable_action' => $action->getManager(),
466                'description'        => $action->getDescription(),
467                'observation'        => $action->getComment(),
468                'createdAt'          => \date_format($action->getCreatedAt(), 'd/m/Y H:i'),
469                'updatedAt'          => \date_format($action->getUpdatedAt(), 'd/m/Y H:i'),
470                'actions'            => $this->generateActionCell($action, $isActionPlan),
471            ];
472        }
473
474        $jsonResponse = new JsonResponse();
475        $jsonResponse->setJson(json_encode($reponse));
476
477        return $jsonResponse;
478    }
479
480    private function generateShowLink(Model\Mesurement $mesurement)
481    {
482        return '<a href="' .
483            $this->router->generate('registry_mesurement_show', ['id' => $mesurement->getId()]) .
484            '">' . \htmlspecialchars($mesurement->getName()) . '</a>';
485    }
486
487    private function generateActionCell(Model\Mesurement $mesurement, bool $isActionPlan = false)
488    {
489        $user = $this->userProvider->getAuthenticatedUser();
490        if ($user->hasAccessTo($mesurement)) {
491            if ($isActionPlan) {
492                return '<a href="' .
493                    $this->router->generate('registry_mesurement_edit', ['id' => $mesurement->getId()]) . '">
494                <i aria-hidden="true" class="fa fa-pencil"></i> ' .
495                    $this->translator->trans('global.action.edit')
496                    . '</a>';
497            }
498
499            return '<a href="' .
500                $this->router->generate('registry_mesurement_edit', ['id' => $mesurement->getId()]) . '">
501            <i aria-hidden="true" class="fa fa-pencil"></i> ' .
502                $this->translator->trans('global.action.edit')
503                . '</a>
504
505            <a href="' .
506                $this->router->generate('registry_mesurement_delete', ['id' => $mesurement->getId()]) .
507                '"><i aria-hidden="true" class="fa fa-trash"></i> ' .
508                $this->translator->trans('global.action.delete')
509                . '</a>';
510        }
511
512        return '';
513    }
514
515    /**
516     * The deletion action
517     * Delete the data.
518     * OVERRIDE of the CRUDController to manage clone id.
519     *
520     * @throws \Exception
521     */
522    public function deleteConfirmationAction(string $id): Response
523    {
524        $object = $this->repository->findOneById($id);
525        if (!$object) {
526            throw new NotFoundHttpException("No object found with ID '{$id}'");
527        }
528
529        /** @var User $user */
530        $user = $this->getUser();
531        if (!$user->hasAccessTo($object)) {
532            return $this->redirectToRoute($this->getRouteName('list'));
533        }
534
535        if ($this->isSoftDelete()) {
536            if (!\method_exists($object, 'setDeletedAt')) {
537                throw new MethodNotImplementedException('setDeletedAt');
538            }
539            $object->setDeletedAt(new \DateTimeImmutable());
540            $this->repository->update($object);
541        } else {
542            /* Delete clonedFrom id from clone to prevent SQL error on foreign key */
543            foreach ($this->repository->findBy(['clonedFrom' => $id]) as $clone) {
544                $clone->setClonedFrom(null);
545            }
546            $this->entityManager->remove($object);
547            $this->entityManager->flush();
548        }
549
550        $this->addFlash('success', $this->getFlashbagMessage('success', 'delete', $object));
551
552        return $this->redirectToRoute($this->getRouteName('list'));
553    }
554}