Completed
Pull Request — master (#6204)
by
unknown
03:46 queued 14s
created

CRUDController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 9.36
c 0
b 0
f 0
cc 1
nc 1
nop 15

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Controller;
15
16
use Doctrine\Inflector\InflectorFactory;
17
use Psr\Log\LoggerInterface;
18
use Psr\Log\NullLogger;
19
use Sonata\AdminBundle\Admin\AdminInterface;
20
use Sonata\AdminBundle\Admin\BreadcrumbsBuilder;
21
use Sonata\AdminBundle\Admin\FieldDescriptionCollection;
22
use Sonata\AdminBundle\Admin\Pool;
23
use Sonata\AdminBundle\Bridge\Exporter\AdminExporter;
24
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
25
use Sonata\AdminBundle\Exception\LockException;
26
use Sonata\AdminBundle\Exception\ModelManagerException;
27
use Sonata\AdminBundle\Model\AuditManagerInterface;
28
use Sonata\AdminBundle\Templating\TemplateRegistryInterface;
29
use Sonata\AdminBundle\Util\AdminObjectAclData;
30
use Sonata\AdminBundle\Util\AdminObjectAclManipulator;
31
use Sonata\AdminBundle\Util\AdminObjectAclUserManager;
32
use Sonata\Exporter\Exporter;
33
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
34
use Symfony\Component\DependencyInjection\ContainerInterface;
35
use Symfony\Component\Form\FormInterface;
36
use Symfony\Component\Form\FormRenderer;
37
use Symfony\Component\Form\FormView;
38
use Symfony\Component\HttpFoundation\JsonResponse;
39
use Symfony\Component\HttpFoundation\RedirectResponse;
40
use Symfony\Component\HttpFoundation\Request;
41
use Symfony\Component\HttpFoundation\RequestStack;
42
use Symfony\Component\HttpFoundation\Response;
43
use Symfony\Component\HttpFoundation\Session\SessionInterface;
44
use Symfony\Component\HttpKernel\Exception\HttpException;
45
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
46
use Symfony\Component\HttpKernel\KernelInterface;
47
use Symfony\Component\PropertyAccess\PropertyAccess;
48
use Symfony\Component\PropertyAccess\PropertyPath;
49
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
50
use Symfony\Component\Security\Csrf\CsrfToken;
51
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
52
use Symfony\Contracts\Translation\TranslatorInterface;
53
use Twig\Environment;
54
55
/**
56
 * @author Thomas Rabaix <[email protected]>
57
 */
58
class CRUDController extends AbstractController
59
{
60
    /**
61
     * @var ContainerInterface
62
     */
63
    protected $container;
64
65
    /**
66
     * The related Admin class.
67
     *
68
     * @var AdminInterface
69
     */
70
    protected $admin;
71
72
    /**
73
     * @var RequestStack
74
     */
75
    private $requestStack;
76
77
    /**
78
     * @var Pool
79
     */
80
    private $pool;
81
82
    /**
83
     * The template registry of the related Admin class.
84
     *
85
     * @var TemplateRegistryInterface
86
     */
87
    private $templateRegistry;
88
89
    /**
90
     * @var BreadcrumbsBuilder
91
     */
92
    private $breadcrumbsBuilder;
93
94
    /**
95
     * @var Environment
96
     */
97
    private $twig;
98
99
    /**
100
     * @var TranslatorInterface
101
     */
102
    private $translator;
103
104
    /**
105
     * @var SessionInterface
106
     */
107
    private $session;
108
109
    /**
110
     * @var KernelInterface
111
     */
112
    private $kernel;
113
114
    /**
115
     * @var AuditManagerInterface
116
     */
117
    private $auditManager;
118
119
    /**
120
     * @var Exporter|null
121
     */
122
    private $exporter;
123
124
    /**
125
     * @var AdminExporter|null
126
     */
127
    private $adminExporter;
128
129
    /**
130
     * @var CsrfTokenManagerInterface|null
131
     */
132
    private $csrfTokenManager;
133
134
    /**
135
     * @var LoggerInterface
136
     */
137
    private $logger;
138
139
    /**
140
     * @var AdminObjectAclManipulator|null
141
     */
142
    private $adminObjectAclManipulator;
143
144
    /**
145
     * @var AdminObjectAclUserManager|null
146
     */
147
    private $adminObjectAclUserManager;
148
149
    public function __construct(
150
        RequestStack $requestStack,
151
        Pool $pool,
152
        TemplateRegistryInterface $templateRegistry,
153
        BreadcrumbsBuilder $breadcrumbsBuilder,
154
        Environment $twig,
155
        TranslatorInterface $translator,
156
        SessionInterface $session,
157
        KernelInterface $kernel,
158
        AuditManagerInterface $auditManager,
159
        ?Exporter $exporter = null,
160
        ?AdminExporter $adminExporter = null,
161
        ?CsrfTokenManagerInterface $csrfTokenManager = null,
162
        ?LoggerInterface $logger = null,
163
        ?AdminObjectAclManipulator $adminObjectAclManipulator = null,
164
        ?AdminObjectAclUserManager $adminObjectAclUserManager = null
165
    ) {
166
        $this->requestStack = $requestStack;
167
        $this->pool = $pool;
168
        $this->templateRegistry = $templateRegistry;
169
        $this->breadcrumbsBuilder = $breadcrumbsBuilder;
170
        $this->twig = $twig;
171
        $this->translator = $translator;
172
        $this->session = $session;
173
        $this->kernel = $kernel;
174
        $this->auditManager = $auditManager;
175
        $this->exporter = $exporter;
176
        $this->adminExporter = $adminExporter;
177
        $this->csrfTokenManager = $csrfTokenManager;
178
        $this->logger = $logger ?? new NullLogger();
179
        $this->adminObjectAclManipulator = $adminObjectAclManipulator;
180
        $this->adminObjectAclUserManager = $adminObjectAclUserManager;
181
182
        $this->configure();
183
    }
184
185
    /**
186
     * Renders a view while passing mandatory parameters on to the template.
187
     *
188
     * @param string               $view       The view name
189
     * @param array<string, mixed> $parameters An array of parameters to pass to the view
190
     */
191
    public function renderWithExtraParams($view, array $parameters = [], ?Response $response = null): Response
192
    {
193
        $response = $response ?? new Response();
194
195
        return $response->setContent($this->twig->render($view, $this->addRenderExtraParams($parameters)));
196
    }
197
198
    /**
199
     * List action.
200
     *
201
     * @throws AccessDeniedException If access is not granted
202
     *
203
     * @return Response
204
     */
205
    public function listAction(Request $request)
206
    {
207
        $this->admin->checkAccess('list');
208
209
        $preResponse = $this->preList($request);
210
        if (null !== $preResponse) {
211
            return $preResponse;
212
        }
213
214
        if ($listMode = $request->get('_list_mode')) {
215
            $this->admin->setListMode($listMode);
216
        }
217
218
        $datagrid = $this->admin->getDatagrid();
219
        $formView = $datagrid->getForm()->createView();
220
221
        // set the theme for the current Admin Form
222
        $this->setFormTheme($formView, $this->admin->getFilterTheme());
223
224
        $template = $this->templateRegistry->getTemplate('list');
225
226
        return $this->renderWithExtraParams($template, [
227
            'action' => 'list',
228
            'form' => $formView,
229
            'datagrid' => $datagrid,
230
            'csrf_token' => $this->getCsrfToken('sonata.batch'),
231
            'export_formats' => $this->adminExporter instanceof Exporter ?
232
                $this->adminExporter->getAvailableFormats($this->admin) :
233
                $this->admin->getExportFormats(),
234
        ]);
235
    }
236
237
    /**
238
     * Execute a batch delete.
239
     *
240
     * @throws AccessDeniedException If access is not granted
241
     *
242
     * @return RedirectResponse
243
     */
244
    public function batchActionDelete(ProxyQueryInterface $query)
245
    {
246
        $this->admin->checkAccess('batchDelete');
247
248
        $modelManager = $this->admin->getModelManager();
249
250
        try {
251
            $modelManager->batchDelete($this->admin->getClass(), $query);
252
            $this->addFlash(
253
                'sonata_flash_success',
254
                $this->trans('flash_batch_delete_success', [], 'SonataAdminBundle')
255
            );
256
        } catch (ModelManagerException $e) {
257
            $this->handleModelManagerException($e);
258
            $this->addFlash(
259
                'sonata_flash_error',
260
                $this->trans('flash_batch_delete_error', [], 'SonataAdminBundle')
261
            );
262
        }
263
264
        return $this->redirectToList();
265
    }
266
267
    /**
268
     * Delete action.
269
     *
270
     * @throws NotFoundHttpException If the object does not exist
271
     * @throws AccessDeniedException If access is not granted
272
     *
273
     * @return Response|RedirectResponse
274
     */
275
    public function deleteAction(Request $request)
276
    {
277
        $id = $request->get($this->admin->getIdParameter());
278
        $object = $this->admin->getObject($id);
279
280
        if (!$object) {
281
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
282
        }
283
284
        $this->checkParentChildAssociation($request, $object);
285
286
        $this->admin->checkAccess('delete', $object);
287
288
        $preResponse = $this->preDelete($request, $object);
289
        if (null !== $preResponse) {
290
            return $preResponse;
291
        }
292
293
        if (Request::METHOD_DELETE === $this->getRestMethod()) {
294
            // check the csrf token
295
            $this->validateCsrfToken('sonata.delete');
296
297
            $objectName = $this->admin->toString($object);
298
299
            try {
300
                $this->admin->delete($object);
301
302
                if ($this->isXmlHttpRequest()) {
303
                    return $this->renderJson(['result' => 'ok'], Response::HTTP_OK, []);
304
                }
305
306
                $this->addFlash(
307
                    'sonata_flash_success',
308
                    $this->trans(
309
                        'flash_delete_success',
310
                        ['%name%' => $this->escapeHtml($objectName)],
311
                        'SonataAdminBundle'
312
                    )
313
                );
314
            } catch (ModelManagerException $e) {
315
                $this->handleModelManagerException($e);
316
317
                if ($this->isXmlHttpRequest()) {
318
                    return $this->renderJson(['result' => 'error'], Response::HTTP_OK, []);
319
                }
320
321
                $this->addFlash(
322
                    'sonata_flash_error',
323
                    $this->trans(
324
                        'flash_delete_error',
325
                        ['%name%' => $this->escapeHtml($objectName)],
326
                        'SonataAdminBundle'
327
                    )
328
                );
329
            }
330
331
            return $this->redirectTo($object);
332
        }
333
334
        $template = $this->templateRegistry->getTemplate('delete');
335
336
        return $this->renderWithExtraParams($template, [
337
            'object' => $object,
338
            'action' => 'delete',
339
            'csrf_token' => $this->getCsrfToken('sonata.delete'),
340
        ]);
341
    }
342
343
    /**
344
     * Edit action.
345
     *
346
     * @throws NotFoundHttpException If the object does not exist
347
     * @throws AccessDeniedException If access is not granted
348
     *
349
     * @return Response|RedirectResponse
350
     */
351
    public function editAction(Request $request)
352
    {
353
        // the key used to lookup the template
354
        $templateKey = 'edit';
355
356
        $id = $request->get($this->admin->getIdParameter());
357
        $existingObject = $this->admin->getObject($id);
358
359
        if (!$existingObject) {
360
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
361
        }
362
363
        $this->checkParentChildAssociation($request, $existingObject);
364
365
        $this->admin->checkAccess('edit', $existingObject);
366
367
        $preResponse = $this->preEdit($request, $existingObject);
368
        if (null !== $preResponse) {
369
            return $preResponse;
370
        }
371
372
        $this->admin->setSubject($existingObject);
373
        $objectId = $this->admin->getNormalizedIdentifier($existingObject);
374
375
        $form = $this->admin->getForm();
376
377
        $form->setData($existingObject);
378
        $form->handleRequest($request);
379
380
        if ($form->isSubmitted()) {
381
            $isFormValid = $form->isValid();
382
383
            // persist if the form was valid and if in preview mode the preview was approved
384
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
385
                $submittedObject = $form->getData();
386
                $this->admin->setSubject($submittedObject);
387
388
                try {
389
                    $existingObject = $this->admin->update($submittedObject);
390
391
                    if ($this->isXmlHttpRequest()) {
392
                        return $this->handleXmlHttpRequestSuccessResponse($request, $existingObject);
393
                    }
394
395
                    $this->addFlash(
396
                        'sonata_flash_success',
397
                        $this->trans(
398
                            'flash_edit_success',
399
                            ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
400
                            'SonataAdminBundle'
401
                        )
402
                    );
403
404
                    // redirect to edit mode
405
                    return $this->redirectTo($existingObject);
406
                } catch (ModelManagerException $e) {
407
                    $this->handleModelManagerException($e);
408
409
                    $isFormValid = false;
410
                } catch (LockException $e) {
411
                    $this->addFlash('sonata_flash_error', $this->trans('flash_lock_error', [
412
                        '%name%' => $this->escapeHtml($this->admin->toString($existingObject)),
413
                        '%link_start%' => sprintf('<a href="%s">', $this->admin->generateObjectUrl('edit', $existingObject)),
414
                        '%link_end%' => '</a>',
415
                    ], 'SonataAdminBundle'));
416
                }
417
            }
418
419
            // show an error message if the form failed validation
420
            if (!$isFormValid) {
421
                if ($this->isXmlHttpRequest() && null !== ($response = $this->handleXmlHttpRequestErrorResponse($request, $form))) {
422
                    return $response;
423
                }
424
425
                $this->addFlash(
426
                    'sonata_flash_error',
427
                    $this->trans(
428
                        'flash_edit_error',
429
                        ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
430
                        'SonataAdminBundle'
431
                    )
432
                );
433
            } elseif ($this->isPreviewRequested()) {
434
                // enable the preview template if the form was valid and preview was requested
435
                $templateKey = 'preview';
436
                $this->admin->getShow();
437
            }
438
        }
439
440
        $formView = $form->createView();
441
        // set the theme for the current Admin Form
442
        $this->setFormTheme($formView, $this->admin->getFormTheme());
443
444
        $template = $this->templateRegistry->getTemplate($templateKey);
445
446
        return $this->renderWithExtraParams($template, [
447
            'action' => 'edit',
448
            'form' => $formView,
449
            'object' => $existingObject,
450
            'objectId' => $objectId,
451
        ]);
452
    }
453
454
    /**
455
     * Batch action.
456
     *
457
     * @throws NotFoundHttpException If the HTTP method is not POST
458
     * @throws \RuntimeException     If the batch action is not defined
459
     *
460
     * @return Response|RedirectResponse
461
     */
462
    public function batchAction(Request $request)
463
    {
464
        $restMethod = $this->getRestMethod();
465
466
        if (Request::METHOD_POST !== $restMethod) {
467
            throw $this->createNotFoundException(sprintf(
468
                'Invalid request method given "%s", %s expected',
469
                $restMethod,
470
                Request::METHOD_POST
471
            ));
472
        }
473
474
        // check the csrf token
475
        $this->validateCsrfToken('sonata.batch');
476
477
        $confirmation = $request->get('confirmation', false);
478
479
        if ($data = json_decode($request->get('data', ''), true)) {
480
            $action = $data['action'];
481
            $idx = $data['idx'];
482
            $allElements = $data['all_elements'];
483
            $request->request->replace(array_merge($request->request->all(), $data));
484
        } else {
485
            $request->request->set('idx', $request->get('idx', []));
486
            $request->request->set('all_elements', $request->get('all_elements', false));
487
488
            $action = $request->get('action');
489
            $idx = $request->get('idx');
490
            $allElements = $request->get('all_elements');
491
            $data = $request->request->all();
492
493
            unset($data['_sonata_csrf_token']);
494
        }
495
496
        $batchActions = $this->admin->getBatchActions();
497
        if (!\array_key_exists($action, $batchActions)) {
498
            throw new \RuntimeException(sprintf('The `%s` batch action is not defined', $action));
499
        }
500
501
        $camelizedAction = InflectorFactory::create()->build()->classify($action);
502
        $isRelevantAction = sprintf('batchAction%sIsRelevant', $camelizedAction);
503
504
        if (method_exists($this, $isRelevantAction)) {
505
            $nonRelevantMessage = $this->$isRelevantAction($idx, $allElements, $request);
506
        } else {
507
            $nonRelevantMessage = 0 !== \count($idx) || $allElements; // at least one item is selected
508
        }
509
510
        if (!$nonRelevantMessage) { // default non relevant message (if false of null)
511
            $nonRelevantMessage = 'flash_batch_empty';
512
        }
513
514
        $datagrid = $this->admin->getDatagrid();
515
        $datagrid->buildPager();
516
517
        if (true !== $nonRelevantMessage) {
518
            $this->addFlash(
519
                'sonata_flash_info',
520
                $this->trans($nonRelevantMessage, [], 'SonataAdminBundle')
521
            );
522
523
            return $this->redirectToList();
524
        }
525
526
        $askConfirmation = $batchActions[$action]['ask_confirmation'] ??
527
            true;
528
529
        if ($askConfirmation && 'ok' !== $confirmation) {
530
            $actionLabel = $batchActions[$action]['label'];
531
            $batchTranslationDomain = $batchActions[$action]['translation_domain'] ??
532
                $this->admin->getTranslationDomain();
533
534
            $formView = $datagrid->getForm()->createView();
535
            $this->setFormTheme($formView, $this->admin->getFilterTheme());
536
537
            $template = !empty($batchActions[$action]['template']) ?
538
                $batchActions[$action]['template'] :
539
                $this->templateRegistry->getTemplate('batch_confirmation');
540
541
            return $this->renderWithExtraParams($template, [
542
                'action' => 'list',
543
                'action_label' => $actionLabel,
544
                'batch_translation_domain' => $batchTranslationDomain,
545
                'datagrid' => $datagrid,
546
                'form' => $formView,
547
                'data' => $data,
548
                'csrf_token' => $this->getCsrfToken('sonata.batch'),
549
            ]);
550
        }
551
552
        // execute the action, batchActionXxxxx
553
        $finalAction = sprintf('batchAction%s', $camelizedAction);
554
        if (!method_exists($this, $finalAction)) {
555
            throw new \RuntimeException(sprintf('A `%s::%s` method must be callable', static::class, $finalAction));
556
        }
557
558
        $query = $datagrid->getQuery();
559
560
        $query->setFirstResult(null);
561
        $query->setMaxResults(null);
562
563
        $this->admin->preBatchAction($action, $query, $idx, $allElements);
564
565
        if (\count($idx) > 0) {
566
            $this->admin->getModelManager()->addIdentifiersToQuery($this->admin->getClass(), $query, $idx);
567
        } elseif (!$allElements) {
568
            $this->addFlash(
569
                'sonata_flash_info',
570
                $this->trans('flash_batch_no_elements_processed', [], 'SonataAdminBundle')
571
            );
572
573
            return $this->redirectToList();
574
        }
575
576
        return $this->$finalAction($query, $request);
577
    }
578
579
    /**
580
     * Create action.
581
     *
582
     * @throws AccessDeniedException If access is not granted
583
     *
584
     * @return Response
585
     */
586
    public function createAction(Request $request)
587
    {
588
        // the key used to lookup the template
589
        $templateKey = 'edit';
590
591
        $this->admin->checkAccess('create');
592
593
        $class = new \ReflectionClass($this->admin->hasActiveSubClass() ? $this->admin->getActiveSubClass() : $this->admin->getClass());
594
595
        if ($class->isAbstract()) {
596
            return $this->renderWithExtraParams(
597
                '@SonataAdmin/CRUD/select_subclass.html.twig',
598
                [
599
                    'base_template' => $this->getBaseTemplate(),
600
                    'admin' => $this->admin,
601
                    'action' => 'create',
602
                ]
603
            );
604
        }
605
606
        $newObject = $this->admin->getNewInstance();
607
608
        $preResponse = $this->preCreate($request, $newObject);
609
        if (null !== $preResponse) {
610
            return $preResponse;
611
        }
612
613
        $this->admin->setSubject($newObject);
614
615
        $form = $this->admin->getForm();
616
617
        $form->setData($newObject);
618
        $form->handleRequest($request);
619
620
        if ($form->isSubmitted()) {
621
            $isFormValid = $form->isValid();
622
623
            // persist if the form was valid and if in preview mode the preview was approved
624
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
625
                $submittedObject = $form->getData();
626
                $this->admin->setSubject($submittedObject);
627
                $this->admin->checkAccess('create', $submittedObject);
628
629
                try {
630
                    $newObject = $this->admin->create($submittedObject);
631
632
                    if ($this->isXmlHttpRequest()) {
633
                        return $this->handleXmlHttpRequestSuccessResponse($request, $newObject);
634
                    }
635
636
                    $this->addFlash(
637
                        'sonata_flash_success',
638
                        $this->trans(
639
                            'flash_create_success',
640
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
641
                            'SonataAdminBundle'
642
                        )
643
                    );
644
645
                    // redirect to edit mode
646
                    return $this->redirectTo($newObject);
647
                } catch (ModelManagerException $e) {
648
                    $this->handleModelManagerException($e);
649
650
                    $isFormValid = false;
651
                }
652
            }
653
654
            // show an error message if the form failed validation
655
            if (!$isFormValid) {
656
                if ($this->isXmlHttpRequest() && null !== ($response = $this->handleXmlHttpRequestErrorResponse($request, $form))) {
657
                    return $response;
658
                }
659
660
                $this->addFlash(
661
                    'sonata_flash_error',
662
                    $this->trans(
663
                        'flash_create_error',
664
                        ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
665
                        'SonataAdminBundle'
666
                    )
667
                );
668
            } elseif ($this->isPreviewRequested()) {
669
                // pick the preview template if the form was valid and preview was requested
670
                $templateKey = 'preview';
671
                $this->admin->getShow();
672
            }
673
        }
674
675
        $formView = $form->createView();
676
        // set the theme for the current Admin Form
677
        $this->setFormTheme($formView, $this->admin->getFormTheme());
678
679
        $template = $this->templateRegistry->getTemplate($templateKey);
680
681
        return $this->renderWithExtraParams($template, [
682
            'action' => 'create',
683
            'form' => $formView,
684
            'object' => $newObject,
685
            'objectId' => null,
686
        ]);
687
    }
688
689
    /**
690
     * Show action.
691
     *
692
     * @throws NotFoundHttpException If the object does not exist
693
     * @throws AccessDeniedException If access is not granted
694
     *
695
     * @return Response
696
     */
697
    public function showAction(Request $request)
698
    {
699
        $id = $request->get($this->admin->getIdParameter());
700
        $object = $this->admin->getObject($id);
701
702
        if (!$object) {
703
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
704
        }
705
706
        $this->checkParentChildAssociation($request, $object);
707
708
        $this->admin->checkAccess('show', $object);
709
710
        $preResponse = $this->preShow($request, $object);
711
        if (null !== $preResponse) {
712
            return $preResponse;
713
        }
714
715
        $this->admin->setSubject($object);
716
717
        $fields = $this->admin->getShow();
718
        \assert($fields instanceof FieldDescriptionCollection);
719
720
        $template = $this->templateRegistry->getTemplate('show');
721
722
        return $this->renderWithExtraParams($template, [
723
            'action' => 'show',
724
            'object' => $object,
725
            'elements' => $fields,
726
        ]);
727
    }
728
729
    /**
730
     * Show history revisions for object.
731
     *
732
     * @throws AccessDeniedException If access is not granted
733
     * @throws NotFoundHttpException If the object does not exist or the audit reader is not available
734
     *
735
     * @return Response
736
     */
737
    public function historyAction(Request $request)
738
    {
739
        $id = $request->get($this->admin->getIdParameter());
740
        $object = $this->admin->getObject($id);
741
742
        if (!$object) {
743
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
744
        }
745
746
        $this->admin->checkAccess('history', $object);
747
748
        $manager = $this->get('sonata.admin.audit.manager');
749
750
        if (!$manager->hasReader($this->admin->getClass())) {
751
            throw $this->createNotFoundException(sprintf(
752
                'unable to find the audit reader for class : %s',
753
                $this->admin->getClass()
754
            ));
755
        }
756
757
        $reader = $this->auditManager->getReader($this->admin->getClass());
758
759
        $revisions = $reader->findRevisions($this->admin->getClass(), $id);
760
761
        $template = $this->templateRegistry->getTemplate('history');
762
763
        return $this->renderWithExtraParams($template, [
764
            'action' => 'history',
765
            'object' => $object,
766
            'revisions' => $revisions,
767
            'currentRevision' => $revisions ? current($revisions) : false,
768
        ]);
769
    }
770
771
    /**
772
     * View history revision of object.
773
     *
774
     * @param string|null $revision
775
     *
776
     * @throws AccessDeniedException If access is not granted
777
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
778
     *
779
     * @return Response
780
     */
781
    public function historyViewRevisionAction(Request $request, $revision = null)
782
    {
783
        $id = $request->get($this->admin->getIdParameter());
784
        $object = $this->admin->getObject($id);
785
786
        if (!$object) {
787
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
788
        }
789
790
        $this->admin->checkAccess('historyViewRevision', $object);
791
792
        $manager = $this->get('sonata.admin.audit.manager');
793
794
        if (!$manager->hasReader($this->admin->getClass())) {
795
            throw $this->createNotFoundException(sprintf(
796
                'unable to find the audit reader for class : %s',
797
                $this->admin->getClass()
798
            ));
799
        }
800
801
        $reader = $this->auditManager->getReader($this->admin->getClass());
802
803
        // retrieve the revisioned object
804
        $object = $reader->find($this->admin->getClass(), $id, $revision);
805
806
        if (!$object) {
807
            throw $this->createNotFoundException(sprintf(
808
                'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
809
                $id,
810
                $revision,
811
                $this->admin->getClass()
812
            ));
813
        }
814
815
        $this->admin->setSubject($object);
816
817
        $template = $this->templateRegistry->getTemplate('show');
818
819
        return $this->renderWithExtraParams($template, [
820
            'action' => 'show',
821
            'object' => $object,
822
            'elements' => $this->admin->getShow(),
823
        ]);
824
    }
825
826
    /**
827
     * Compare history revisions of object.
828
     *
829
     * @param int|string|null $baseRevision
830
     * @param int|string|null $compareRevision
831
     *
832
     * @throws AccessDeniedException If access is not granted
833
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
834
     *
835
     * @return Response
836
     */
837
    public function historyCompareRevisionsAction(Request $request, $baseRevision = null, $compareRevision = null)
838
    {
839
        $this->admin->checkAccess('historyCompareRevisions');
840
841
        $id = $request->get($this->admin->getIdParameter());
842
        $object = $this->admin->getObject($id);
843
844
        if (!$object) {
845
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
846
        }
847
848
        $manager = $this->get('sonata.admin.audit.manager');
849
850
        if (!$manager->hasReader($this->admin->getClass())) {
851
            throw $this->createNotFoundException(sprintf(
852
                'unable to find the audit reader for class : %s',
853
                $this->admin->getClass()
854
            ));
855
        }
856
857
        $reader = $this->auditManager->getReader($this->admin->getClass());
858
859
        // retrieve the base revision
860
        $baseObject = $reader->find($this->admin->getClass(), $id, $baseRevision);
861
        if (!$baseObject) {
862
            throw $this->createNotFoundException(
863
                sprintf(
864
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
865
                    $id,
866
                    $baseRevision,
867
                    $this->admin->getClass()
868
                )
869
            );
870
        }
871
872
        // retrieve the compare revision
873
        $compareObject = $reader->find($this->admin->getClass(), $id, $compareRevision);
874
        if (!$compareObject) {
875
            throw $this->createNotFoundException(
876
                sprintf(
877
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
878
                    $id,
879
                    $compareRevision,
880
                    $this->admin->getClass()
881
                )
882
            );
883
        }
884
885
        $this->admin->setSubject($baseObject);
886
887
        $template = $this->templateRegistry->getTemplate('show_compare');
888
889
        return $this->renderWithExtraParams($template, [
890
            'action' => 'show',
891
            'object' => $baseObject,
892
            'object_compare' => $compareObject,
893
            'elements' => $this->admin->getShow(),
894
        ]);
895
    }
896
897
    /**
898
     * Export data to specified format.
899
     *
900
     * @throws AccessDeniedException If access is not granted
901
     * @throws \RuntimeException     If the export format is invalid
902
     *
903
     * @return Response
904
     */
905
    public function exportAction(Request $request)
906
    {
907
        $this->admin->checkAccess('export');
908
909
        $format = $request->get('format');
910
911
        $allowedExportFormats = $this->adminExporter->getAvailableFormats($this->admin);
912
        $filename = $this->adminExporter->getExportFilename($this->admin, $format);
913
914
        if (!\in_array($format, $allowedExportFormats, true)) {
915
            throw new \RuntimeException(sprintf(
916
                'Export in format `%s` is not allowed for class: `%s`. Allowed formats are: `%s`',
917
                $format,
918
                $this->admin->getClass(),
919
                implode(', ', $allowedExportFormats)
920
            ));
921
        }
922
923
        return $this->exporter->getResponse(
924
            $format,
925
            $filename,
926
            $this->admin->getDataSourceIterator()
927
        );
928
    }
929
930
    /**
931
     * Returns the Response object associated to the acl action.
932
     *
933
     * @throws AccessDeniedException If access is not granted
934
     * @throws NotFoundHttpException If the object does not exist or the ACL is not enabled
935
     *
936
     * @return Response|RedirectResponse
937
     */
938
    public function aclAction(Request $request)
939
    {
940
        if (!$this->admin->isAclEnabled()) {
941
            throw $this->createNotFoundException('ACL are not enabled for this admin');
942
        }
943
944
        $id = $request->get($this->admin->getIdParameter());
945
        $object = $this->admin->getObject($id);
946
947
        if (!$object) {
948
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
949
        }
950
951
        $this->admin->checkAccess('acl', $object);
952
953
        $this->admin->setSubject($object);
954
        $aclUsers = $this->getAclUsers();
955
        $aclRoles = $this->getAclRoles();
956
957
        $adminObjectAclData = new AdminObjectAclData(
958
            $this->admin,
959
            $object,
960
            $aclUsers,
961
            $this->adminObjectAclManipulator->getMaskBuilderClass(),
962
            $aclRoles
963
        );
964
965
        $aclUsersForm = $this->adminObjectAclManipulator->createAclUsersForm($adminObjectAclData);
966
        $aclRolesForm = $this->adminObjectAclManipulator->createAclRolesForm($adminObjectAclData);
967
968
        if (Request::METHOD_POST === $request->getMethod()) {
969
            if ($request->request->has(AdminObjectAclManipulator::ACL_USERS_FORM_NAME)) {
970
                $form = $aclUsersForm;
971
                $updateMethod = 'updateAclUsers';
972
            } elseif ($request->request->has(AdminObjectAclManipulator::ACL_ROLES_FORM_NAME)) {
973
                $form = $aclRolesForm;
974
                $updateMethod = 'updateAclRoles';
975
            }
976
977
            if (isset($form, $updateMethod)) {
978
                $form->handleRequest($request);
979
980
                if ($form->isValid()) {
981
                    $this->adminObjectAclManipulator->$updateMethod($adminObjectAclData);
982
                    $this->addFlash(
983
                        'sonata_flash_success',
984
                        $this->trans('flash_acl_edit_success', [], 'SonataAdminBundle')
985
                    );
986
987
                    return new RedirectResponse($this->admin->generateObjectUrl('acl', $object));
988
                }
989
            }
990
        }
991
992
        $template = $this->templateRegistry->getTemplate('acl');
993
994
        return $this->renderWithExtraParams($template, [
995
            'action' => 'acl',
996
            'permissions' => $adminObjectAclData->getUserPermissions(),
997
            'object' => $object,
998
            'users' => $aclUsers,
999
            'roles' => $aclRoles,
1000
            'aclUsersForm' => $aclUsersForm->createView(),
1001
            'aclRolesForm' => $aclRolesForm->createView(),
1002
        ]);
1003
    }
1004
1005
    public function getRequest(): ?Request
1006
    {
1007
        return $this->requestStack->getCurrentRequest();
1008
    }
1009
1010
    protected function addFlash(string $type, $message): void
1011
    {
1012
        $this->session->set($type, $message);
1013
    }
1014
1015
    /**
1016
     * @param array<string, mixed> $parameters
1017
     *
1018
     * @return array<string, mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1019
     */
1020
    protected function addRenderExtraParams(array $parameters = []): array
1021
    {
1022
        if (!$this->isXmlHttpRequest()) {
1023
            $parameters['breadcrumbs_builder'] = $this->breadcrumbsBuilder;
1024
        }
1025
1026
        $parameters['admin'] = $parameters['admin'] ?? $this->admin;
1027
        $parameters['base_template'] = $parameters['base_template'] ?? $this->getBaseTemplate();
1028
        $parameters['admin_pool'] = $this->pool;
1029
1030
        return $parameters;
1031
    }
1032
1033
    /**
1034
     * Gets a container configuration parameter by its name.
1035
     *
1036
     * @param string $name The parameter name
1037
     *
1038
     * @return mixed
1039
     */
1040
    protected function getParameter($name)
1041
    {
1042
        return $this->container->getParameter($name);
1043
    }
1044
1045
    /**
1046
     * Render JSON.
1047
     *
1048
     * @param mixed $data
1049
     * @param int   $status
1050
     * @param array $headers
1051
     *
1052
     * @return JsonResponse with json encoded data
1053
     */
1054
    protected function renderJson($data, $status = Response::HTTP_OK, $headers = [])
1055
    {
1056
        return new JsonResponse($data, $status, $headers);
1057
    }
1058
1059
    /**
1060
     * Returns true if the request is a XMLHttpRequest.
1061
     *
1062
     * @return bool True if the request is an XMLHttpRequest, false otherwise
1063
     */
1064
    protected function isXmlHttpRequest()
1065
    {
1066
        $request = $this->getRequest();
1067
1068
        return $request->isXmlHttpRequest() || $request->get('_xml_http_request');
1069
    }
1070
1071
    /**
1072
     * Returns the correct RESTful verb, given either by the request itself or
1073
     * via the "_method" parameter.
1074
     *
1075
     * @return string HTTP method, either
1076
     */
1077
    protected function getRestMethod()
1078
    {
1079
        $request = $this->getRequest();
1080
1081
        if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) {
1082
            return $request->getMethod();
1083
        }
1084
1085
        return $request->request->get('_method');
1086
    }
1087
1088
    /**
1089
     * Contextualize the admin class depends on the current request.
1090
     *
1091
     * @throws \RuntimeException
1092
     */
1093
    protected function configure(): void
1094
    {
1095
        $request = $this->getRequest();
1096
1097
        $adminCode = $request->get('_sonata_admin');
1098
1099
        if (!$adminCode) {
1100
            throw new \RuntimeException(sprintf(
1101
                'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`',
1102
                static::class,
1103
                $request->get('_route')
1104
            ));
1105
        }
1106
1107
        try {
1108
            $this->admin = $this->pool->getAdminByAdminCode($adminCode);
1109
        } catch (\InvalidArgumentException $e) {
1110
            throw new \RuntimeException(sprintf(
1111
                'Unable to find the admin class related to the current controller (%s)',
1112
                static::class
1113
            ));
1114
        }
1115
1116
        $this->templateRegistry = $this->container->get(sprintf('%s.template_registry', $this->admin->getCode()));
1117
        if (!$this->templateRegistry instanceof TemplateRegistryInterface) {
1118
            throw new \RuntimeException(sprintf(
1119
                'Unable to find the template registry related to the current admin (%s)',
1120
                $this->admin->getCode()
1121
            ));
1122
        }
1123
1124
        $rootAdmin = $this->admin;
1125
1126
        while ($rootAdmin->isChild()) {
1127
            $rootAdmin->setCurrentChild(true);
1128
            $rootAdmin = $rootAdmin->getParent();
1129
        }
1130
1131
        $rootAdmin->setRequest($request);
1132
1133
        if ($request->get('uniqid')) {
1134
            $this->admin->setUniqid($request->get('uniqid'));
1135
        }
1136
    }
1137
1138
    /**
1139
     * Proxy for the logger service of the container.
1140
     * If no such service is found, a NullLogger is returned.
1141
     */
1142
    protected function getLogger(): LoggerInterface
1143
    {
1144
        return $this->logger;
1145
    }
1146
1147
    /**
1148
     * Returns the base template name.
1149
     *
1150
     * @return string The template name
1151
     */
1152
    protected function getBaseTemplate()
1153
    {
1154
        if ($this->isXmlHttpRequest()) {
1155
            return $this->templateRegistry->getTemplate('ajax');
1156
        }
1157
1158
        return $this->templateRegistry->getTemplate('layout');
1159
    }
1160
1161
    /**
1162
     * @throws \Exception
1163
     */
1164
    protected function handleModelManagerException(\Exception $e): void
1165
    {
1166
        if ($this->kernel->isDebug()) {
1167
            throw $e;
1168
        }
1169
1170
        $context = ['exception' => $e];
1171
        if ($e->getPrevious()) {
1172
            $context['previous_exception_message'] = $e->getPrevious()->getMessage();
1173
        }
1174
        $this->getLogger()->error($e->getMessage(), $context);
1175
    }
1176
1177
    /**
1178
     * Redirect the user depend on this choice.
1179
     *
1180
     * @param object $object
1181
     *
1182
     * @return RedirectResponse
1183
     */
1184
    protected function redirectTo($object)
1185
    {
1186
        $request = $this->getRequest();
1187
1188
        $url = false;
1189
1190
        if (null !== $request->get('btn_update_and_list')) {
1191
            return $this->redirectToList();
1192
        }
1193
        if (null !== $request->get('btn_create_and_list')) {
1194
            return $this->redirectToList();
1195
        }
1196
1197
        if (null !== $request->get('btn_create_and_create')) {
1198
            $params = [];
1199
            if ($this->admin->hasActiveSubClass()) {
1200
                $params['subclass'] = $request->get('subclass');
1201
            }
1202
            $url = $this->admin->generateUrl('create', $params);
1203
        }
1204
1205
        if ('DELETE' === $this->getRestMethod()) {
1206
            return $this->redirectToList();
1207
        }
1208
1209
        if (!$url) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $url of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1210
            foreach (['edit', 'show'] as $route) {
1211
                if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route, $object)) {
1212
                    $url = $this->admin->generateObjectUrl(
1213
                        $route,
1214
                        $object,
1215
                        $this->getSelectedTab($request)
0 ignored issues
show
Bug introduced by
It seems like $request defined by $this->getRequest() on line 1186 can be null; however, Sonata\AdminBundle\Contr...oller::getSelectedTab() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1216
                    );
1217
1218
                    break;
1219
                }
1220
            }
1221
        }
1222
1223
        if (!$url) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $url of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1224
            return $this->redirectToList();
1225
        }
1226
1227
        return new RedirectResponse($url);
1228
    }
1229
1230
    /**
1231
     * Redirects the user to the list view.
1232
     *
1233
     * @return RedirectResponse
1234
     */
1235
    final protected function redirectToList()
1236
    {
1237
        $parameters = [];
1238
1239
        if ($filter = $this->admin->getFilterParameters()) {
1240
            $parameters['filter'] = $filter;
1241
        }
1242
1243
        return $this->redirect($this->admin->generateUrl('list', $parameters));
1244
    }
1245
1246
    /**
1247
     * Returns true if the preview is requested to be shown.
1248
     *
1249
     * @return bool
1250
     */
1251
    protected function isPreviewRequested()
1252
    {
1253
        $request = $this->getRequest();
1254
1255
        return null !== $request->get('btn_preview');
1256
    }
1257
1258
    /**
1259
     * Returns true if the preview has been approved.
1260
     *
1261
     * @return bool
1262
     */
1263
    protected function isPreviewApproved()
1264
    {
1265
        $request = $this->getRequest();
1266
1267
        return null !== $request->get('btn_preview_approve');
1268
    }
1269
1270
    /**
1271
     * Returns true if the request is in the preview workflow.
1272
     *
1273
     * That means either a preview is requested or the preview has already been shown
1274
     * and it got approved/declined.
1275
     *
1276
     * @return bool
1277
     */
1278
    protected function isInPreviewMode()
1279
    {
1280
        return $this->admin->supportsPreviewMode()
1281
        && ($this->isPreviewRequested()
1282
            || $this->isPreviewApproved()
1283
            || $this->isPreviewDeclined());
1284
    }
1285
1286
    /**
1287
     * Returns true if the preview has been declined.
1288
     *
1289
     * @return bool
1290
     */
1291
    protected function isPreviewDeclined()
1292
    {
1293
        $request = $this->getRequest();
1294
1295
        return null !== $request->get('btn_preview_decline');
1296
    }
1297
1298
    /**
1299
     * Gets ACL users.
1300
     */
1301
    protected function getAclUsers(): \Traversable
1302
    {
1303
        $aclUsers = $this->adminObjectAclUserManager instanceof AdminObjectAclUserManager ?
1304
            $this->adminObjectAclUserManager->findUsers() :
1305
            [];
1306
1307
        return \is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
1308
    }
1309
1310
    /**
1311
     * Gets ACL roles.
1312
     */
1313
    protected function getAclRoles(): \Traversable
1314
    {
1315
        $aclRoles = [];
1316
        $roleHierarchy = $this->pool->getContainer()->getParameter('security.role_hierarchy.roles');
1317
1318
        foreach ($this->pool->getAdminServiceIds() as $id) {
1319
            try {
1320
                $admin = $this->pool->getInstance($id);
1321
            } catch (\Exception $e) {
1322
                continue;
1323
            }
1324
1325
            $baseRole = $admin->getSecurityHandler()->getBaseRole($admin);
1326
            foreach ($admin->getSecurityInformation() as $role => $permissions) {
1327
                $role = sprintf($baseRole, $role);
1328
                $aclRoles[] = $role;
1329
            }
1330
        }
1331
1332
        foreach ($roleHierarchy as $name => $roles) {
1333
            $aclRoles[] = $name;
1334
            $aclRoles = array_merge($aclRoles, $roles);
1335
        }
1336
1337
        $aclRoles = array_unique($aclRoles);
1338
1339
        return \is_array($aclRoles) ? new \ArrayIterator($aclRoles) : $aclRoles;
1340
    }
1341
1342
    /**
1343
     * Validate CSRF token for action without form.
1344
     *
1345
     * @param string $intention
1346
     *
1347
     * @throws HttpException
1348
     */
1349
    protected function validateCsrfToken($intention): void
1350
    {
1351
        $request = $this->getRequest();
1352
        $token = $request->get('_sonata_csrf_token');
1353
1354
        if ($this->csrfTokenManager instanceof CsrfTokenManagerInterface) {
1355
            $valid = $this->csrfTokenManager->isTokenValid(new CsrfToken($intention, $token));
1356
        } else {
1357
            return;
1358
        }
1359
1360
        if (!$valid) {
1361
            throw new HttpException(Response::HTTP_BAD_REQUEST, 'The csrf token is not valid, CSRF attack?');
1362
        }
1363
    }
1364
1365
    /**
1366
     * Escape string for html output.
1367
     *
1368
     * @param string $s
1369
     *
1370
     * @return string
1371
     */
1372
    protected function escapeHtml($s)
1373
    {
1374
        return htmlspecialchars((string) $s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1375
    }
1376
1377
    /**
1378
     * Get CSRF token.
1379
     *
1380
     * @param string $intention
1381
     *
1382
     * @return string|false
1383
     */
1384
    protected function getCsrfToken($intention)
1385
    {
1386
        if ($this->csrfTokenManager instanceof CsrfTokenManagerInterface) {
1387
            return $this->csrfTokenManager->getToken($intention)->getValue();
1388
        }
1389
1390
        return false;
1391
    }
1392
1393
    /**
1394
     * This method can be overloaded in your custom CRUD controller.
1395
     * It's called from createAction.
1396
     *
1397
     * @param object $object
1398
     *
1399
     * @return Response|null
1400
     */
1401
    protected function preCreate(Request $request, $object)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1402
    {
1403
        return null;
1404
    }
1405
1406
    /**
1407
     * This method can be overloaded in your custom CRUD controller.
1408
     * It's called from editAction.
1409
     *
1410
     * @param object $object
1411
     *
1412
     * @return Response|null
1413
     */
1414
    protected function preEdit(Request $request, $object)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1415
    {
1416
        return null;
1417
    }
1418
1419
    /**
1420
     * This method can be overloaded in your custom CRUD controller.
1421
     * It's called from deleteAction.
1422
     *
1423
     * @param object $object
1424
     *
1425
     * @return Response|null
1426
     */
1427
    protected function preDelete(Request $request, $object)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1428
    {
1429
        return null;
1430
    }
1431
1432
    /**
1433
     * This method can be overloaded in your custom CRUD controller.
1434
     * It's called from showAction.
1435
     *
1436
     * @param object $object
1437
     *
1438
     * @return Response|null
1439
     */
1440
    protected function preShow(Request $request, $object)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1441
    {
1442
        return null;
1443
    }
1444
1445
    /**
1446
     * This method can be overloaded in your custom CRUD controller.
1447
     * It's called from listAction.
1448
     *
1449
     * @return Response|null
1450
     */
1451
    protected function preList(Request $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1452
    {
1453
        return null;
1454
    }
1455
1456
    /**
1457
     * Translate a message id.
1458
     *
1459
     * @param string $id
1460
     * @param string $domain
1461
     * @param string $locale
1462
     *
1463
     * @return string translated string
1464
     */
1465
    final protected function trans($id, array $parameters = [], $domain = null, $locale = null)
1466
    {
1467
        $domain = $domain ?: $this->admin->getTranslationDomain();
1468
1469
        return $this->translator->trans($id, $parameters, $domain, $locale);
1470
    }
1471
1472
    private function getSelectedTab(Request $request): array
1473
    {
1474
        return array_filter(['_tab' => $request->request->get('_tab')]);
1475
    }
1476
1477
    private function checkParentChildAssociation(Request $request, $object): void
1478
    {
1479
        if (!$this->admin->isChild()) {
1480
            return;
1481
        }
1482
1483
        $parentAdmin = $this->admin->getParent();
1484
        $parentId = $request->get($parentAdmin->getIdParameter());
1485
1486
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
1487
        $propertyPath = new PropertyPath($this->admin->getParentAssociationMapping());
1488
1489
        if ($parentAdmin->getObject($parentId) !== $propertyAccessor->getValue($object, $propertyPath)) {
1490
            throw new \RuntimeException(sprintf(
1491
                'There is no association between "%s" and "%s"',
1492
                $parentAdmin->toString($parentAdmin->getObject($parentId)),
1493
                $this->admin->toString($object)
1494
            ));
1495
        }
1496
    }
1497
1498
    /**
1499
     * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
1500
     */
1501
    private function setFormTheme(FormView $formView, ?array $theme = null): void
1502
    {
1503
        $this->twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme);
1504
    }
1505
1506
    private function handleXmlHttpRequestErrorResponse(Request $request, FormInterface $form): JsonResponse
1507
    {
1508
        if (!\in_array('application/json', $request->getAcceptableContentTypes(), true)) {
1509
            return $this->renderJson([], Response::HTTP_NOT_ACCEPTABLE);
1510
        }
1511
1512
        $errors = [];
1513
        foreach ($form->getErrors(true) as $error) {
1514
            $errors[] = $error->getMessage();
1515
        }
1516
1517
        return $this->renderJson([
1518
            'result' => 'error',
1519
            'errors' => $errors,
1520
        ], Response::HTTP_BAD_REQUEST);
1521
    }
1522
1523
    /**
1524
     * @param object $object
1525
     */
1526
    private function handleXmlHttpRequestSuccessResponse(Request $request, $object): JsonResponse
1527
    {
1528
        if (!\in_array('application/json', $request->getAcceptableContentTypes(), true)) {
1529
            return $this->renderJson([], Response::HTTP_NOT_ACCEPTABLE);
1530
        }
1531
1532
        return $this->renderJson([
1533
            'result' => 'ok',
1534
            'objectId' => $this->admin->getNormalizedIdentifier($object),
1535
            'objectName' => $this->escapeHtml($this->admin->toString($object)),
1536
        ], Response::HTTP_OK);
1537
    }
1538
}
1539