Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
29.88% |
49 / 164 |
|
35.00% |
7 / 20 |
CRAP | |
0.00% |
0 / 1 |
CRUDController | |
29.88% |
49 / 164 |
|
35.00% |
7 / 20 |
1137.28 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
getDomain | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getModel | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getModelClass | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getFormType | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getTemplatingBasePath | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getFlashbagMessage | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
getRouteName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getListData | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
listAction | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
formPrePersistData | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createAction | |
61.90% |
13 / 21 |
|
0.00% |
0 / 1 |
6.38 | |||
editAction | |
66.67% |
16 / 24 |
|
0.00% |
0 / 1 |
10.37 | |||
showAction | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
110 | |||
deleteAction | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
deleteConfirmationAction | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
30 | |||
pdfAction | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
getPdfName | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
isSoftDelete | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getNotifications | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
deleteAllAction | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
deleteConfirmationAllAction | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
pdfAllAction | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
archiveAllAction | |
0.00% |
0 / 12 |
|
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 | |
22 | declare(strict_types=1); |
23 | |
24 | namespace App\Application\Controller; |
25 | |
26 | use App\Application\DDD\Repository\RepositoryInterface; |
27 | use App\Application\Doctrine\Repository\CRUDRepository; |
28 | use App\Application\Interfaces\CollectivityRelated; |
29 | use App\Application\Symfony\Security\UserProvider; |
30 | use App\Domain\Notification\Model\Notification; |
31 | use App\Domain\Tools\ChainManipulator; |
32 | use App\Domain\User\Model\Collectivity; |
33 | use App\Domain\User\Model\User; |
34 | use Doctrine\ORM\EntityManagerInterface; |
35 | use Knp\Bundle\SnappyBundle\Snappy\Response\PdfResponse; |
36 | use Knp\Snappy\Pdf; |
37 | use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; |
38 | use Symfony\Component\HttpFoundation\Request; |
39 | use Symfony\Component\HttpFoundation\Response; |
40 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; |
41 | use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; |
42 | use Symfony\Contracts\Translation\TranslatorInterface; |
43 | use Symfony\Polyfill\Intl\Icu\Exception\MethodNotImplementedException; |
44 | |
45 | abstract class CRUDController extends AbstractController |
46 | { |
47 | /** |
48 | * @var EntityManagerInterface |
49 | */ |
50 | protected $entityManager; |
51 | |
52 | /** |
53 | * @var TranslatorInterface |
54 | */ |
55 | protected $translator; |
56 | |
57 | /** |
58 | * @var CRUDRepository |
59 | */ |
60 | protected $repository; |
61 | |
62 | /** |
63 | * @var Pdf |
64 | */ |
65 | protected $pdf; |
66 | |
67 | /** |
68 | * @var UserProvider |
69 | */ |
70 | protected $userProvider; |
71 | |
72 | /** |
73 | * @var AuthorizationCheckerInterface |
74 | */ |
75 | protected $authorizationChecker; |
76 | |
77 | /** |
78 | * CRUDController constructor. |
79 | */ |
80 | public function __construct( |
81 | EntityManagerInterface $entityManager, |
82 | TranslatorInterface $translator, |
83 | RepositoryInterface $repository, |
84 | Pdf $pdf, |
85 | UserProvider $userProvider, |
86 | AuthorizationCheckerInterface $authorizationChecker, |
87 | ) { |
88 | $this->entityManager = $entityManager; |
89 | $this->translator = $translator; |
90 | $this->repository = $repository; |
91 | $this->pdf = $pdf; |
92 | $this->userProvider = $userProvider; |
93 | $this->authorizationChecker = $authorizationChecker; |
94 | } |
95 | |
96 | /** |
97 | * Get the domain of the object. |
98 | * (Formatted as string word). |
99 | */ |
100 | abstract protected function getDomain(): string; |
101 | |
102 | /** |
103 | * Get the model of the object. |
104 | * (Formatted as string word). |
105 | */ |
106 | abstract protected function getModel(): string; |
107 | |
108 | /** |
109 | * Get the model class name of the object. |
110 | * This methods return the class name with it namespace. |
111 | */ |
112 | abstract protected function getModelClass(): string; |
113 | |
114 | /** |
115 | * Get the form type class name to use during create & edit action. |
116 | */ |
117 | abstract protected function getFormType(): string; |
118 | |
119 | /** |
120 | * Generate the templating base path dynamically depending on the domain, model & template. |
121 | * |
122 | * @param string|null $template The template to display |
123 | * |
124 | * @return string The generated templating base path |
125 | */ |
126 | protected function getTemplatingBasePath(?string $template = null): string |
127 | { |
128 | // TODO: Check template existence |
129 | $domain = \ucfirst($this->getDomain()); |
130 | $model = \ucfirst($this->getModel()); |
131 | |
132 | return "{$domain}/{$model}/{$template}.html.twig"; |
133 | } |
134 | |
135 | /** |
136 | * Generate the flashbag message dynamically depending on the domain, model & object. |
137 | * Replace word `%object%` in translation by the related object (thanks to it `__toString` method). |
138 | * |
139 | * @param string $type The flashbag type |
140 | * @param string|null $template The related template to use |
141 | * @param mixed|null $object The object to use to generate flashbag (eg. show object name) |
142 | * |
143 | * @return string The generated flashbag |
144 | */ |
145 | protected function getFlashbagMessage(string $type, ?string $template = null, $object = null): string |
146 | { |
147 | $params = []; |
148 | if (!\is_null($object)) { |
149 | $params['%object%'] = $object; |
150 | } |
151 | |
152 | return $this->translator->trans( |
153 | "{$this->getDomain()}.{$this->getModel()}.flashbag.{$type}.{$template}", |
154 | $params |
155 | ); |
156 | } |
157 | |
158 | /** |
159 | * Generate route name depending on the template. |
160 | * |
161 | * @param string|null $template The template to use for generation |
162 | * |
163 | * @return string The generated route name |
164 | */ |
165 | protected function getRouteName(?string $template = null): string |
166 | { |
167 | return "{$this->getDomain()}_{$this->getModel()}_{$template}"; |
168 | } |
169 | |
170 | /** |
171 | * Get data to use in List view. |
172 | * |
173 | * @return array |
174 | */ |
175 | protected function getListData() |
176 | { |
177 | return $this->repository->findAll(); |
178 | } |
179 | |
180 | /** |
181 | * The list action view |
182 | * Get data & display them. |
183 | */ |
184 | public function listAction(): Response |
185 | { |
186 | return $this->render($this->getTemplatingBasePath('list'), [ |
187 | 'objects' => $this->getListData(), |
188 | ]); |
189 | } |
190 | |
191 | /** |
192 | * Actions to make when a form is submitted and valid. |
193 | * This method is handled just after form validation, before object manipulation. |
194 | */ |
195 | public function formPrePersistData($object, $form = null) |
196 | { |
197 | } |
198 | |
199 | /** |
200 | * The creation action view |
201 | * Create a new data. |
202 | */ |
203 | public function createAction(Request $request): Response |
204 | { |
205 | $modelClass = $this->getModelClass(); |
206 | /** @var CollectivityRelated $object */ |
207 | $object = new $modelClass(); |
208 | $serviceEnabled = false; |
209 | |
210 | if ($object instanceof CollectivityRelated) { |
211 | $user = $this->userProvider->getAuthenticatedUser(); |
212 | $object->setCollectivity($user->getCollectivity()); |
213 | $serviceEnabled = $object->getCollectivity() && $object->getCollectivity()->getIsServicesEnabled(); |
214 | } |
215 | |
216 | $form = $this->createForm($this->getFormType(), $object, ['validation_groups' => ['default', $this->getModel(), 'create']]); |
217 | |
218 | $form->handleRequest($request); |
219 | |
220 | if ($form->isSubmitted() && $form->isValid()) { |
221 | $this->formPrePersistData($object, $form); |
222 | $em = $this->getDoctrine()->getManager(); |
223 | |
224 | $em->persist($object); |
225 | $em->flush(); |
226 | |
227 | $this->addFlash('success', $this->getFlashbagMessage('success', 'create', $object)); |
228 | |
229 | return $this->redirectToRoute($this->getRouteName('list')); |
230 | } |
231 | |
232 | return $this->render($this->getTemplatingBasePath('create'), [ |
233 | 'form' => $form->createView(), |
234 | 'object' => $object, |
235 | 'serviceEnabled' => $serviceEnabled, |
236 | ]); |
237 | } |
238 | |
239 | /** |
240 | * The edition action view |
241 | * Edit an existing data. |
242 | * |
243 | * @param string $id The ID of the data to edit |
244 | */ |
245 | public function editAction(Request $request, string $id): Response |
246 | { |
247 | // /** @var CollectivityRelated $object */ |
248 | $object = $this->repository->findOneById($id); |
249 | if (!$object) { |
250 | throw new NotFoundHttpException("No object found with ID '{$id}'"); |
251 | } |
252 | |
253 | $serviceEnabled = false; |
254 | |
255 | /** @var User $user */ |
256 | $user = $this->getUser(); |
257 | if (!$user->hasAccessTo($object)) { |
258 | return $this->redirectToRoute($this->getRouteName('list')); |
259 | } |
260 | |
261 | if ($object instanceof Collectivity) { |
262 | $serviceEnabled = $object->getIsServicesEnabled(); |
263 | } elseif ($object instanceof CollectivityRelated) { |
264 | $serviceEnabled = $object->getCollectivity() && $object->getCollectivity()->getIsServicesEnabled(); |
265 | } |
266 | |
267 | $form = $this->createForm($this->getFormType(), $object, ['validation_groups' => ['default', $this->getModel(), 'edit']]); |
268 | |
269 | $form->handleRequest($request); |
270 | |
271 | if ($form->isSubmitted() && $form->isValid()) { |
272 | $this->formPrePersistData($object, $form); |
273 | $this->entityManager->persist($object); |
274 | $this->entityManager->flush(); |
275 | |
276 | $this->addFlash('success', $this->getFlashbagMessage('success', 'edit', $object)); |
277 | |
278 | return $this->redirectToRoute($this->getRouteName('list')); |
279 | } |
280 | |
281 | return $this->render($this->getTemplatingBasePath('edit'), [ |
282 | 'form' => $form->createView(), |
283 | 'object' => $object, |
284 | 'serviceEnabled' => $serviceEnabled, |
285 | ]); |
286 | } |
287 | |
288 | /** |
289 | * The show action view |
290 | * Display the object information. |
291 | * |
292 | * @param string $id The ID of the data to display |
293 | */ |
294 | public function showAction(string $id): Response |
295 | { |
296 | /** @var CollectivityRelated $object */ |
297 | $object = $this->repository->findOneById($id); |
298 | if (!$object) { |
299 | throw new NotFoundHttpException("No object found with ID '{$id}'"); |
300 | } |
301 | /** |
302 | * @var User $user |
303 | */ |
304 | $user = $this->getUser(); |
305 | if (!$user->hasAccessTo($object, false)) { |
306 | throw $this->createAccessDeniedException(); |
307 | } |
308 | $serviceEnabled = false; |
309 | if ($object instanceof Collectivity) { |
310 | $serviceEnabled = $object->getIsServicesEnabled(); |
311 | } elseif ($object instanceof CollectivityRelated && $object->getCollectivity()) { |
312 | $serviceEnabled = $object->getCollectivity()->getIsServicesEnabled(); |
313 | } |
314 | |
315 | $actionEnabled = true; |
316 | |
317 | if ($object instanceof CollectivityRelated && !$this->authorizationChecker->isGranted('ROLE_ADMIN') && !$user->getServices()->isEmpty()) { |
318 | $actionEnabled = $object->isInUserServices($this->userProvider->getAuthenticatedUser()); |
319 | } |
320 | |
321 | if (!$this->isGranted('ROLE_USER')) { |
322 | $actionEnabled = false; |
323 | } |
324 | |
325 | return $this->render($this->getTemplatingBasePath('show'), [ |
326 | 'object' => $object, |
327 | 'actionEnabled' => $actionEnabled, |
328 | 'serviceEnabled' => $serviceEnabled, |
329 | ]); |
330 | } |
331 | |
332 | /** |
333 | * The delete action view |
334 | * Display a confirmation message to confirm data deletion. |
335 | */ |
336 | public function deleteAction(string $id): Response |
337 | { |
338 | /** @var CollectivityRelated $object */ |
339 | $object = $this->repository->findOneById($id); |
340 | if (!$object) { |
341 | throw new NotFoundHttpException("No object found with ID '{$id}'"); |
342 | } |
343 | /** |
344 | * @var User $user |
345 | */ |
346 | $user = $this->getUser(); |
347 | |
348 | if (!$user->hasAccessTo($object)) { |
349 | return $this->redirectToRoute($this->getRouteName('list')); |
350 | } |
351 | |
352 | return $this->render($this->getTemplatingBasePath('delete'), [ |
353 | 'object' => $object, |
354 | 'id' => $id, |
355 | ]); |
356 | } |
357 | |
358 | /** |
359 | * The deletion action |
360 | * Delete the data. |
361 | * |
362 | * @throws \Exception |
363 | */ |
364 | public function deleteConfirmationAction(string $id): Response |
365 | { |
366 | $object = $this->repository->findOneById($id); |
367 | if (!$object) { |
368 | throw new NotFoundHttpException("No object found with ID '{$id}'"); |
369 | } |
370 | /** |
371 | * @var User $user |
372 | */ |
373 | $user = $this->getUser(); |
374 | if (!$user->hasAccessTo($object)) { |
375 | return $this->redirectToRoute($this->getRouteName('list')); |
376 | } |
377 | |
378 | if ($this->isSoftDelete()) { |
379 | if (!\method_exists($object, 'setDeletedAt')) { |
380 | throw new MethodNotImplementedException('setDeletedAt'); |
381 | } |
382 | $object->setDeletedAt(new \DateTimeImmutable()); |
383 | $this->repository->update($object); |
384 | } else { |
385 | $this->entityManager->remove($object); |
386 | $this->entityManager->flush(); |
387 | } |
388 | |
389 | $this->addFlash('success', $this->getFlashbagMessage('success', 'delete', $object)); |
390 | |
391 | return $this->redirectToRoute($this->getRouteName('list')); |
392 | } |
393 | |
394 | public function pdfAction(string $id) |
395 | { |
396 | $object = $this->repository->findOneById($id); |
397 | if (!$object) { |
398 | throw new NotFoundHttpException("No object found with ID '{$id}'"); |
399 | } |
400 | /** @var User $user */ |
401 | $user = $this->getUser(); |
402 | if (!$user->hasAccessTo($object, false)) { |
403 | return $this->redirectToRoute($this->getRouteName('list')); |
404 | } |
405 | |
406 | return new PdfResponse( |
407 | $this->pdf->getOutputFromHtml( |
408 | $this->renderView($this->getTemplatingBasePath('pdf'), ['object' => $object]) |
409 | ), |
410 | $this->getPdfName((string) $object) . '.pdf' |
411 | ); |
412 | } |
413 | |
414 | public function getPdfName(string $name): string |
415 | { |
416 | $name = ChainManipulator::removeAllNonAlphaNumericChar(ChainManipulator::removeAccents($name)); |
417 | |
418 | return $name . '-' . date('mdY'); |
419 | } |
420 | |
421 | /** |
422 | * Check if we have to produce a soft delete behaviour. |
423 | */ |
424 | protected function isSoftDelete(): bool |
425 | { |
426 | return false; |
427 | } |
428 | |
429 | public function getNotifications(): array |
430 | { |
431 | return $this->entityManager->getRepository(Notification::class)->findAll(); |
432 | } |
433 | |
434 | /** |
435 | * The delete action list |
436 | * Display a confirmation message to confirm data deletion. |
437 | */ |
438 | public function deleteAllAction(Request $request): Response |
439 | { |
440 | $ids = $request->query->get('ids'); |
441 | $ids = explode(',', $ids); |
442 | |
443 | return $this->render($this->getTemplatingBasePath('delete_all'), [ // delete_all |
444 | 'ids' => $ids, |
445 | 'objects_length' => count($ids), |
446 | ]); |
447 | } |
448 | |
449 | public function deleteConfirmationAllAction(Request $request): Response |
450 | { |
451 | $ids = $request->query->get('ids'); |
452 | |
453 | foreach ($ids as $id) { |
454 | $this->deleteConfirmationAction($id); |
455 | } |
456 | |
457 | return $this->redirectToRoute($this->getRouteName('list')); |
458 | } |
459 | |
460 | // RETURN A PDF WITH ALL IDS FILTERED |
461 | public function pdfAllAction(Request $request) |
462 | { |
463 | $ids = $request->query->get('ids'); |
464 | $ids = explode(',', $ids); |
465 | |
466 | $objects = []; |
467 | |
468 | foreach ($ids as $id) { |
469 | $object = $this->repository->findOneById($id); |
470 | if ($this->userProvider->getAuthenticatedUser()->hasAccessTo($object, false)) { |
471 | array_push($objects, $object); |
472 | } |
473 | } |
474 | |
475 | return new PdfResponse( |
476 | $this->pdf->getOutputFromHtml( |
477 | $this->renderView($this->getTemplatingBasePath('pdf_all'), ['objects' => $objects]) |
478 | ), |
479 | $this->getPdfName((string) 'print') . '.pdf' |
480 | ); |
481 | } |
482 | |
483 | /** |
484 | * The archive action |
485 | * Display a confirmation message to confirm data archived. |
486 | */ |
487 | public function archiveAllAction(Request $request): Response |
488 | { |
489 | $ids = $request->query->get('ids'); |
490 | $ids = explode(',', $ids); |
491 | |
492 | if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) { |
493 | $this->addFlash('error', 'Vous ne pouvez pas archiver ces traitements'); |
494 | |
495 | return $this->redirectToRoute($this->getRouteName('list')); |
496 | } |
497 | |
498 | foreach ($ids as $id) { |
499 | $object = $this->repository->findOneById($id); |
500 | if ($object && $this->userProvider->getAuthenticatedUser()->hasAccessTo($object)) { |
501 | $object->setActive(false); |
502 | $this->addFlash('success', $this->getFlashbagMessage('success', 'delete', $object)); |
503 | } |
504 | } |
505 | $this->entityManager->flush(); |
506 | |
507 | return $this->redirectToRoute($this->getRouteName('list')); |
508 | } |
509 | } |