Completed
Pull Request — master (#6093)
by Mathieu
02:42
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\Session;
44
use Symfony\Component\HttpFoundation\Session\SessionInterface;
45
use Symfony\Component\HttpKernel\Exception\HttpException;
46
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
47
use Symfony\Component\HttpKernel\KernelInterface;
48
use Symfony\Component\PropertyAccess\PropertyAccess;
49
use Symfony\Component\PropertyAccess\PropertyPath;
50
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
51
use Symfony\Component\Security\Csrf\CsrfToken;
52
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
53
use Symfony\Contracts\Translation\TranslatorInterface;
54
use Twig\Environment;
55
56
/**
57
 * @author Thomas Rabaix <[email protected]>
58
 */
59
class CRUDController extends AbstractController
60
{
61
    /**
62
     * The related Admin class.
63
     *
64
     * @var AdminInterface
65
     */
66
    protected $admin;
67
68
    /**
69
     * @var RequestStack
70
     */
71
    private $requestStack;
72
73
    /**
74
     * @var Pool
75
     */
76
    private $pool;
77
78
    /**
79
     * The template registry of the related Admin class.
80
     *
81
     * @var TemplateRegistryInterface
82
     */
83
    private $templateRegistry;
84
85
    /**
86
     * @var BreadcrumbsBuilder
87
     */
88
    private $breadcrumbsBuilder;
89
90
    /**
91
     * @var Environment
92
     */
93
    private $twig;
94
95
    /**
96
     * @var TranslatorInterface
97
     */
98
    private $translator;
99
100
    /**
101
     * @var SessionInterface
102
     */
103
    private $session;
104
105
    /**
106
     * @var KernelInterface
107
     */
108
    private $kernel;
109
110
    /**
111
     * @var AuditManagerInterface
112
     */
113
    private $auditManager;
114
115
    /**
116
     * @var Exporter|null
117
     */
118
    private $exporter;
119
120
    /**
121
     * @var AdminExporter|null
122
     */
123
    private $adminExporter;
124
125
    /**
126
     * @var CsrfTokenManagerInterface|null
127
     */
128
    private $csrfTokenManager;
129
130
    /**
131
     * @var LoggerInterface
132
     */
133
    private $logger;
134
135
    /**
136
     * @var AdminObjectAclManipulator|null
137
     */
138
    private $adminObjectAclManipulator;
139
140
    /**
141
     * @var AdminObjectAclUserManager|null
142
     */
143
    private $adminObjectAclUserManager;
144
145
    public function __construct(
146
        RequestStack $requestStack,
147
        Pool $pool,
148
        TemplateRegistryInterface $templateRegistry,
149
        BreadcrumbsBuilder $breadcrumbsBuilder,
150
        Environment $twig,
151
        TranslatorInterface $translator,
152
        SessionInterface $session,
153
        KernelInterface $kernel,
154
        AuditManagerInterface $auditManager,
155
        ?Exporter $exporter = null,
156
        ?AdminExporter $adminExporter = null,
157
        ?CsrfTokenManagerInterface $csrfTokenManager = null,
158
        ?LoggerInterface $logger = null,
159
        ?AdminObjectAclManipulator $adminObjectAclManipulator = null,
160
        ?AdminObjectAclUserManager $adminObjectAclUserManager = null
161
    ) {
162
        $this->requestStack = $requestStack;
163
        $this->pool = $pool;
164
        $this->templateRegistry = $templateRegistry;
165
        $this->breadcrumbsBuilder = $breadcrumbsBuilder;
166
        $this->twig = $twig;
167
        $this->translator = $translator;
168
        $this->session = $session;
169
        $this->kernel = $kernel;
170
        $this->auditManager = $auditManager;
171
        $this->exporter = $exporter;
172
        $this->adminExporter = $adminExporter;
173
        $this->csrfTokenManager = $csrfTokenManager;
174
        $this->logger = $logger ?? new NullLogger();
175
        $this->adminObjectAclManipulator = $adminObjectAclManipulator;
176
        $this->adminObjectAclUserManager = $adminObjectAclUserManager;
177
178
        $this->configure();
179
    }
180
181
    /**
182
     * Renders a view while passing mandatory parameters on to the template.
183
     *
184
     * @param string               $view       The view name
185
     * @param array<string, mixed> $parameters An array of parameters to pass to the view
186
     */
187
    public function renderWithExtraParams($view, array $parameters = [], ?Response $response = null): Response
188
    {
189
        $response = $response ?? new Response();
190
191
        return $response->setContent($this->twig->render($view, $this->addRenderExtraParams($parameters)));
192
    }
193
194
    /**
195
     * List action.
196
     *
197
     * @throws AccessDeniedException If access is not granted
198
     *
199
     * @return Response
200
     */
201
    public function listAction(Request $request)
202
    {
203
        $this->admin->checkAccess('list');
204
205
        $preResponse = $this->preList($request);
206
        if (null !== $preResponse) {
207
            return $preResponse;
208
        }
209
210
        if ($listMode = $request->get('_list_mode')) {
211
            $this->admin->setListMode($listMode);
212
        }
213
214
        $datagrid = $this->admin->getDatagrid();
215
        $formView = $datagrid->getForm()->createView();
216
217
        // set the theme for the current Admin Form
218
        $this->setFormTheme($formView, $this->admin->getFilterTheme());
219
220
        $template = $this->templateRegistry->getTemplate('list');
221
222
        return $this->renderWithExtraParams($template, [
223
            'action' => 'list',
224
            'form' => $formView,
225
            'datagrid' => $datagrid,
226
            'csrf_token' => $this->getCsrfToken('sonata.batch'),
227
            'export_formats' => $this->adminExporter ?
228
                $this->adminExporter->getAvailableFormats($this->admin) :
229
                $this->admin->getExportFormats(),
230
        ]);
231
    }
232
233
    /**
234
     * Execute a batch delete.
235
     *
236
     * @throws AccessDeniedException If access is not granted
237
     *
238
     * @return RedirectResponse
239
     */
240
    public function batchActionDelete(ProxyQueryInterface $query)
241
    {
242
        $this->admin->checkAccess('batchDelete');
243
244
        $modelManager = $this->admin->getModelManager();
245
246
        try {
247
            $modelManager->batchDelete($this->admin->getClass(), $query);
248
            $this->addFlash(
249
                'sonata_flash_success',
250
                $this->trans('flash_batch_delete_success', [], 'SonataAdminBundle')
251
            );
252
        } catch (ModelManagerException $e) {
253
            $this->handleModelManagerException($e);
254
            $this->addFlash(
255
                'sonata_flash_error',
256
                $this->trans('flash_batch_delete_error', [], 'SonataAdminBundle')
257
            );
258
        }
259
260
        return $this->redirectToList();
261
    }
262
263
    /**
264
     * Delete action.
265
     *
266
     * @throws NotFoundHttpException If the object does not exist
267
     * @throws AccessDeniedException If access is not granted
268
     *
269
     * @return Response|RedirectResponse
270
     */
271
    public function deleteAction(Request $request)
272
    {
273
        $id = $request->get($this->admin->getIdParameter());
274
        $object = $this->admin->getObject($id);
275
276
        if (!$object) {
277
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
278
        }
279
280
        $this->checkParentChildAssociation($request, $object);
281
282
        $this->admin->checkAccess('delete', $object);
283
284
        $preResponse = $this->preDelete($request, $object);
285
        if (null !== $preResponse) {
286
            return $preResponse;
287
        }
288
289
        if (Request::METHOD_DELETE === $this->getRestMethod()) {
290
            // check the csrf token
291
            $this->validateCsrfToken('sonata.delete');
292
293
            $objectName = $this->admin->toString($object);
294
295
            try {
296
                $this->admin->delete($object);
297
298
                if ($this->isXmlHttpRequest()) {
299
                    return $this->renderJson(['result' => 'ok'], Response::HTTP_OK, []);
300
                }
301
302
                $this->addFlash(
303
                    'sonata_flash_success',
304
                    $this->trans(
305
                        'flash_delete_success',
306
                        ['%name%' => $this->escapeHtml($objectName)],
307
                        'SonataAdminBundle'
308
                    )
309
                );
310
            } catch (ModelManagerException $e) {
311
                $this->handleModelManagerException($e);
312
313
                if ($this->isXmlHttpRequest()) {
314
                    return $this->renderJson(['result' => 'error'], Response::HTTP_OK, []);
315
                }
316
317
                $this->addFlash(
318
                    'sonata_flash_error',
319
                    $this->trans(
320
                        'flash_delete_error',
321
                        ['%name%' => $this->escapeHtml($objectName)],
322
                        'SonataAdminBundle'
323
                    )
324
                );
325
            }
326
327
            return $this->redirectTo($object);
328
        }
329
330
        $template = $this->templateRegistry->getTemplate('delete');
331
332
        return $this->renderWithExtraParams($template, [
333
            'object' => $object,
334
            'action' => 'delete',
335
            'csrf_token' => $this->getCsrfToken('sonata.delete'),
336
        ]);
337
    }
338
339
    /**
340
     * Edit action.
341
     *
342
     * @throws NotFoundHttpException If the object does not exist
343
     * @throws AccessDeniedException If access is not granted
344
     *
345
     * @return Response|RedirectResponse
346
     */
347
    public function editAction(Request $request)
348
    {
349
        // the key used to lookup the template
350
        $templateKey = 'edit';
351
352
        $id = $request->get($this->admin->getIdParameter());
353
        $existingObject = $this->admin->getObject($id);
354
355
        if (!$existingObject) {
356
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
357
        }
358
359
        $this->checkParentChildAssociation($request, $existingObject);
360
361
        $this->admin->checkAccess('edit', $existingObject);
362
363
        $preResponse = $this->preEdit($request, $existingObject);
364
        if (null !== $preResponse) {
365
            return $preResponse;
366
        }
367
368
        $this->admin->setSubject($existingObject);
369
        $objectId = $this->admin->getNormalizedIdentifier($existingObject);
370
371
        $form = $this->admin->getForm();
372
373
        $form->setData($existingObject);
374
        $form->handleRequest($request);
375
376
        if ($form->isSubmitted()) {
377
            $isFormValid = $form->isValid();
378
379
            // persist if the form was valid and if in preview mode the preview was approved
380
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
381
                $submittedObject = $form->getData();
382
                $this->admin->setSubject($submittedObject);
383
384
                try {
385
                    $existingObject = $this->admin->update($submittedObject);
386
387
                    if ($this->isXmlHttpRequest()) {
388
                        return $this->handleXmlHttpRequestSuccessResponse($request, $existingObject);
389
                    }
390
391
                    $this->addFlash(
392
                        'sonata_flash_success',
393
                        $this->trans(
394
                            'flash_edit_success',
395
                            ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
396
                            'SonataAdminBundle'
397
                        )
398
                    );
399
400
                    // redirect to edit mode
401
                    return $this->redirectTo($existingObject);
402
                } catch (ModelManagerException $e) {
403
                    $this->handleModelManagerException($e);
404
405
                    $isFormValid = false;
406
                } catch (LockException $e) {
407
                    $this->addFlash('sonata_flash_error', $this->trans('flash_lock_error', [
408
                        '%name%' => $this->escapeHtml($this->admin->toString($existingObject)),
409
                        '%link_start%' => sprintf('<a href="%s">', $this->admin->generateObjectUrl('edit', $existingObject)),
410
                        '%link_end%' => '</a>',
411
                    ], 'SonataAdminBundle'));
412
                }
413
            }
414
415
            // show an error message if the form failed validation
416
            if (!$isFormValid) {
417
                if ($this->isXmlHttpRequest() && null !== ($response = $this->handleXmlHttpRequestErrorResponse($request, $form))) {
418
                    return $response;
419
                }
420
421
                $this->addFlash(
422
                    'sonata_flash_error',
423
                    $this->trans(
424
                        'flash_edit_error',
425
                        ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
426
                        'SonataAdminBundle'
427
                    )
428
                );
429
            } elseif ($this->isPreviewRequested()) {
430
                // enable the preview template if the form was valid and preview was requested
431
                $templateKey = 'preview';
432
                $this->admin->getShow();
433
            }
434
        }
435
436
        $formView = $form->createView();
437
        // set the theme for the current Admin Form
438
        $this->setFormTheme($formView, $this->admin->getFormTheme());
439
440
        $template = $this->templateRegistry->getTemplate($templateKey);
441
442
        return $this->renderWithExtraParams($template, [
443
            'action' => 'edit',
444
            'form' => $formView,
445
            'object' => $existingObject,
446
            'objectId' => $objectId,
447
        ]);
448
    }
449
450
    /**
451
     * Batch action.
452
     *
453
     * @throws NotFoundHttpException If the HTTP method is not POST
454
     * @throws \RuntimeException     If the batch action is not defined
455
     *
456
     * @return Response|RedirectResponse
457
     */
458
    public function batchAction(Request $request)
459
    {
460
        $restMethod = $this->getRestMethod();
461
462
        if (Request::METHOD_POST !== $restMethod) {
463
            throw $this->createNotFoundException(sprintf(
464
                'Invalid request method given "%s", %s expected',
465
                $restMethod,
466
                Request::METHOD_POST
467
            ));
468
        }
469
470
        // check the csrf token
471
        $this->validateCsrfToken('sonata.batch');
472
473
        $confirmation = $request->get('confirmation', false);
474
475
        if ($data = json_decode($request->get('data', ''), true)) {
476
            $action = $data['action'];
477
            $idx = $data['idx'];
478
            $allElements = $data['all_elements'];
479
            $request->request->replace(array_merge($request->request->all(), $data));
480
        } else {
481
            $request->request->set('idx', $request->get('idx', []));
482
            $request->request->set('all_elements', $request->get('all_elements', false));
483
484
            $action = $request->get('action');
485
            $idx = $request->get('idx');
486
            $allElements = $request->get('all_elements');
487
            $data = $request->request->all();
488
489
            unset($data['_sonata_csrf_token']);
490
        }
491
492
        $batchActions = $this->admin->getBatchActions();
493
        if (!\array_key_exists($action, $batchActions)) {
494
            throw new \RuntimeException(sprintf('The `%s` batch action is not defined', $action));
495
        }
496
497
        $camelizedAction = InflectorFactory::create()->build()->classify($action);
498
        $isRelevantAction = sprintf('batchAction%sIsRelevant', $camelizedAction);
499
500
        if (method_exists($this, $isRelevantAction)) {
501
            $nonRelevantMessage = $this->$isRelevantAction($idx, $allElements, $request);
502
        } else {
503
            $nonRelevantMessage = 0 !== \count($idx) || $allElements; // at least one item is selected
504
        }
505
506
        if (!$nonRelevantMessage) { // default non relevant message (if false of null)
507
            $nonRelevantMessage = 'flash_batch_empty';
508
        }
509
510
        $datagrid = $this->admin->getDatagrid();
511
        $datagrid->buildPager();
512
513
        if (true !== $nonRelevantMessage) {
514
            $this->addFlash(
515
                'sonata_flash_info',
516
                $this->trans($nonRelevantMessage, [], 'SonataAdminBundle')
517
            );
518
519
            return $this->redirectToList();
520
        }
521
522
        $askConfirmation = $batchActions[$action]['ask_confirmation'] ??
523
            true;
524
525
        if ($askConfirmation && 'ok' !== $confirmation) {
526
            $actionLabel = $batchActions[$action]['label'];
527
            $batchTranslationDomain = $batchActions[$action]['translation_domain'] ??
528
                $this->admin->getTranslationDomain();
529
530
            $formView = $datagrid->getForm()->createView();
531
            $this->setFormTheme($formView, $this->admin->getFilterTheme());
532
533
            $template = !empty($batchActions[$action]['template']) ?
534
                $batchActions[$action]['template'] :
535
                $this->templateRegistry->getTemplate('batch_confirmation');
536
537
            return $this->renderWithExtraParams($template, [
538
                'action' => 'list',
539
                'action_label' => $actionLabel,
540
                'batch_translation_domain' => $batchTranslationDomain,
541
                'datagrid' => $datagrid,
542
                'form' => $formView,
543
                'data' => $data,
544
                'csrf_token' => $this->getCsrfToken('sonata.batch'),
545
            ]);
546
        }
547
548
        // execute the action, batchActionXxxxx
549
        $finalAction = sprintf('batchAction%s', $camelizedAction);
550
        if (!method_exists($this, $finalAction)) {
551
            throw new \RuntimeException(sprintf('A `%s::%s` method must be callable', static::class, $finalAction));
552
        }
553
554
        $query = $datagrid->getQuery();
555
556
        $query->setFirstResult(null);
557
        $query->setMaxResults(null);
558
559
        $this->admin->preBatchAction($action, $query, $idx, $allElements);
560
561
        if (\count($idx) > 0) {
562
            $this->admin->getModelManager()->addIdentifiersToQuery($this->admin->getClass(), $query, $idx);
563
        } elseif (!$allElements) {
564
            $this->addFlash(
565
                'sonata_flash_info',
566
                $this->trans('flash_batch_no_elements_processed', [], 'SonataAdminBundle')
567
            );
568
569
            return $this->redirectToList();
570
        }
571
572
        return $this->$finalAction($query, $request);
573
    }
574
575
    /**
576
     * Create action.
577
     *
578
     * @throws AccessDeniedException If access is not granted
579
     *
580
     * @return Response
581
     */
582
    public function createAction(Request $request)
583
    {
584
        // the key used to lookup the template
585
        $templateKey = 'edit';
586
587
        $this->admin->checkAccess('create');
588
589
        $class = new \ReflectionClass($this->admin->hasActiveSubClass() ? $this->admin->getActiveSubClass() : $this->admin->getClass());
590
591
        if ($class->isAbstract()) {
592
            return $this->renderWithExtraParams(
593
                '@SonataAdmin/CRUD/select_subclass.html.twig',
594
                [
595
                    'base_template' => $this->getBaseTemplate(),
596
                    'admin' => $this->admin,
597
                    'action' => 'create',
598
                ]
599
            );
600
        }
601
602
        $newObject = $this->admin->getNewInstance();
603
604
        $preResponse = $this->preCreate($request, $newObject);
605
        if (null !== $preResponse) {
606
            return $preResponse;
607
        }
608
609
        $this->admin->setSubject($newObject);
610
611
        $form = $this->admin->getForm();
612
613
        $form->setData($newObject);
614
        $form->handleRequest($request);
615
616
        if ($form->isSubmitted()) {
617
            $isFormValid = $form->isValid();
618
619
            // persist if the form was valid and if in preview mode the preview was approved
620
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
621
                $submittedObject = $form->getData();
622
                $this->admin->setSubject($submittedObject);
623
                $this->admin->checkAccess('create', $submittedObject);
624
625
                try {
626
                    $newObject = $this->admin->create($submittedObject);
627
628
                    if ($this->isXmlHttpRequest()) {
629
                        return $this->handleXmlHttpRequestSuccessResponse($request, $newObject);
630
                    }
631
632
                    $this->addFlash(
633
                        'sonata_flash_success',
634
                        $this->trans(
635
                            'flash_create_success',
636
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
637
                            'SonataAdminBundle'
638
                        )
639
                    );
640
641
                    // redirect to edit mode
642
                    return $this->redirectTo($newObject);
643
                } catch (ModelManagerException $e) {
644
                    $this->handleModelManagerException($e);
645
646
                    $isFormValid = false;
647
                }
648
            }
649
650
            // show an error message if the form failed validation
651
            if (!$isFormValid) {
652
                if ($this->isXmlHttpRequest() && null !== ($response = $this->handleXmlHttpRequestErrorResponse($request, $form))) {
653
                    return $response;
654
                }
655
656
                $this->addFlash(
657
                    'sonata_flash_error',
658
                    $this->trans(
659
                        'flash_create_error',
660
                        ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
661
                        'SonataAdminBundle'
662
                    )
663
                );
664
            } elseif ($this->isPreviewRequested()) {
665
                // pick the preview template if the form was valid and preview was requested
666
                $templateKey = 'preview';
667
                $this->admin->getShow();
668
            }
669
        }
670
671
        $formView = $form->createView();
672
        // set the theme for the current Admin Form
673
        $this->setFormTheme($formView, $this->admin->getFormTheme());
674
675
        $template = $this->templateRegistry->getTemplate($templateKey);
676
677
        return $this->renderWithExtraParams($template, [
678
            'action' => 'create',
679
            'form' => $formView,
680
            'object' => $newObject,
681
            'objectId' => null,
682
        ]);
683
    }
684
685
    /**
686
     * Show action.
687
     *
688
     * @throws NotFoundHttpException If the object does not exist
689
     * @throws AccessDeniedException If access is not granted
690
     *
691
     * @return Response
692
     */
693
    public function showAction(Request $request)
694
    {
695
        $id = $request->get($this->admin->getIdParameter());
696
        $object = $this->admin->getObject($id);
697
698
        if (!$object) {
699
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
700
        }
701
702
        $this->checkParentChildAssociation($request, $object);
703
704
        $this->admin->checkAccess('show', $object);
705
706
        $preResponse = $this->preShow($request, $object);
707
        if (null !== $preResponse) {
708
            return $preResponse;
709
        }
710
711
        $this->admin->setSubject($object);
712
713
        $fields = $this->admin->getShow();
714
        \assert($fields instanceof FieldDescriptionCollection);
715
716
        $template = $this->templateRegistry->getTemplate('show');
717
718
        return $this->renderWithExtraParams($template, [
719
            'action' => 'show',
720
            'object' => $object,
721
            'elements' => $fields,
722
        ]);
723
    }
724
725
    /**
726
     * Show history revisions for object.
727
     *
728
     * @throws AccessDeniedException If access is not granted
729
     * @throws NotFoundHttpException If the object does not exist or the audit reader is not available
730
     *
731
     * @return Response
732
     */
733
    public function historyAction(Request $request)
734
    {
735
        $id = $request->get($this->admin->getIdParameter());
736
        $object = $this->admin->getObject($id);
737
738
        if (!$object) {
739
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
740
        }
741
742
        $this->admin->checkAccess('history', $object);
743
744
        if (!$this->auditManager->hasReader($this->admin->getClass())) {
745
            throw $this->createNotFoundException(
746
                sprintf(
747
                    'unable to find the audit reader for class : %s',
748
                    $this->admin->getClass()
749
                )
750
            );
751
        }
752
753
        $reader = $this->auditManager->getReader($this->admin->getClass());
754
755
        $revisions = $reader->findRevisions($this->admin->getClass(), $id);
756
757
        $template = $this->templateRegistry->getTemplate('history');
758
759
        return $this->renderWithExtraParams($template, [
760
            'action' => 'history',
761
            'object' => $object,
762
            'revisions' => $revisions,
763
            'currentRevision' => $revisions ? current($revisions) : false,
764
        ]);
765
    }
766
767
    /**
768
     * View history revision of object.
769
     *
770
     * @param string|null $revision
771
     *
772
     * @throws AccessDeniedException If access is not granted
773
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
774
     *
775
     * @return Response
776
     */
777
    public function historyViewRevisionAction(Request $request, $revision = null)
778
    {
779
        $id = $request->get($this->admin->getIdParameter());
780
        $object = $this->admin->getObject($id);
781
782
        if (!$object) {
783
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
784
        }
785
786
        $this->admin->checkAccess('historyViewRevision', $object);
787
788
        if (!$this->auditManager->hasReader($this->admin->getClass())) {
789
            throw $this->createNotFoundException(
790
                sprintf(
791
                    'unable to find the audit reader for class : %s',
792
                    $this->admin->getClass()
793
                )
794
            );
795
        }
796
797
        $reader = $this->auditManager->getReader($this->admin->getClass());
798
799
        // retrieve the revisioned object
800
        $object = $reader->find($this->admin->getClass(), $id, $revision);
801
802
        if (!$object) {
803
            throw $this->createNotFoundException(sprintf(
804
                'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
805
                $id,
806
                $revision,
807
                $this->admin->getClass()
808
            ));
809
        }
810
811
        $this->admin->setSubject($object);
812
813
        $template = $this->templateRegistry->getTemplate('show');
814
815
        return $this->renderWithExtraParams($template, [
816
            'action' => 'show',
817
            'object' => $object,
818
            'elements' => $this->admin->getShow(),
819
        ]);
820
    }
821
822
    /**
823
     * Compare history revisions of object.
824
     *
825
     * @param int|string|null $baseRevision
826
     * @param int|string|null $compareRevision
827
     *
828
     * @throws AccessDeniedException If access is not granted
829
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
830
     *
831
     * @return Response
832
     */
833
    public function historyCompareRevisionsAction(Request $request, $baseRevision = null, $compareRevision = null)
834
    {
835
        $this->admin->checkAccess('historyCompareRevisions');
836
837
        $id = $request->get($this->admin->getIdParameter());
838
        $object = $this->admin->getObject($id);
839
840
        if (!$object) {
841
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
842
        }
843
844
        if (!$this->auditManager->hasReader($this->admin->getClass())) {
845
            throw $this->createNotFoundException(
846
                sprintf(
847
                    'unable to find the audit reader for class : %s',
848
                    $this->admin->getClass()
849
                )
850
            );
851
        }
852
853
        $reader = $this->auditManager->getReader($this->admin->getClass());
854
855
        // retrieve the base revision
856
        $baseObject = $reader->find($this->admin->getClass(), $id, $baseRevision);
857
        if (!$baseObject) {
858
            throw $this->createNotFoundException(
859
                sprintf(
860
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
861
                    $id,
862
                    $baseRevision,
863
                    $this->admin->getClass()
864
                )
865
            );
866
        }
867
868
        // retrieve the compare revision
869
        $compareObject = $reader->find($this->admin->getClass(), $id, $compareRevision);
870
        if (!$compareObject) {
871
            throw $this->createNotFoundException(
872
                sprintf(
873
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
874
                    $id,
875
                    $compareRevision,
876
                    $this->admin->getClass()
877
                )
878
            );
879
        }
880
881
        $this->admin->setSubject($baseObject);
882
883
        $template = $this->templateRegistry->getTemplate('show_compare');
884
885
        return $this->renderWithExtraParams($template, [
886
            'action' => 'show',
887
            'object' => $baseObject,
888
            'object_compare' => $compareObject,
889
            'elements' => $this->admin->getShow(),
890
        ]);
891
    }
892
893
    /**
894
     * Export data to specified format.
895
     *
896
     * @throws AccessDeniedException If access is not granted
897
     * @throws \RuntimeException     If the export format is invalid
898
     *
899
     * @return Response
900
     */
901
    public function exportAction(Request $request)
902
    {
903
        $this->admin->checkAccess('export');
904
905
        $format = $request->get('format');
906
907
        $allowedExportFormats = $this->adminExporter->getAvailableFormats($this->admin);
908
        $filename = $this->adminExporter->getExportFilename($this->admin, $format);
909
910
        if (!\in_array($format, $allowedExportFormats, true)) {
911
            throw new \RuntimeException(sprintf(
912
                'Export in format `%s` is not allowed for class: `%s`. Allowed formats are: `%s`',
913
                $format,
914
                $this->admin->getClass(),
915
                implode(', ', $allowedExportFormats)
916
            ));
917
        }
918
919
        return $this->exporter->getResponse(
920
            $format,
921
            $filename,
922
            $this->admin->getDataSourceIterator()
923
        );
924
    }
925
926
    /**
927
     * Returns the Response object associated to the acl action.
928
     *
929
     * @throws AccessDeniedException If access is not granted
930
     * @throws NotFoundHttpException If the object does not exist or the ACL is not enabled
931
     *
932
     * @return Response|RedirectResponse
933
     */
934
    public function aclAction(Request $request)
935
    {
936
        if (!$this->admin->isAclEnabled()) {
937
            throw $this->createNotFoundException('ACL are not enabled for this admin');
938
        }
939
940
        $id = $request->get($this->admin->getIdParameter());
941
        $object = $this->admin->getObject($id);
942
943
        if (!$object) {
944
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
945
        }
946
947
        $this->admin->checkAccess('acl', $object);
948
949
        $this->admin->setSubject($object);
950
        $aclUsers = $this->getAclUsers();
951
        $aclRoles = $this->getAclRoles();
952
953
        $adminObjectAclData = new AdminObjectAclData(
954
            $this->admin,
955
            $object,
956
            $aclUsers,
957
            $this->adminObjectAclManipulator->getMaskBuilderClass(),
958
            $aclRoles
959
        );
960
961
        $aclUsersForm = $this->adminObjectAclManipulator->createAclUsersForm($adminObjectAclData);
962
        $aclRolesForm = $this->adminObjectAclManipulator->createAclRolesForm($adminObjectAclData);
963
964
        if (Request::METHOD_POST === $request->getMethod()) {
965
            if ($request->request->has(AdminObjectAclManipulator::ACL_USERS_FORM_NAME)) {
966
                $form = $aclUsersForm;
967
                $updateMethod = 'updateAclUsers';
968
            } elseif ($request->request->has(AdminObjectAclManipulator::ACL_ROLES_FORM_NAME)) {
969
                $form = $aclRolesForm;
970
                $updateMethod = 'updateAclRoles';
971
            }
972
973
            if (isset($form, $updateMethod)) {
974
                $form->handleRequest($request);
975
976
                if ($form->isValid()) {
977
                    $this->adminObjectAclManipulator->$updateMethod($adminObjectAclData);
978
                    $this->addFlash(
979
                        'sonata_flash_success',
980
                        $this->trans('flash_acl_edit_success', [], 'SonataAdminBundle')
981
                    );
982
983
                    return new RedirectResponse($this->admin->generateObjectUrl('acl', $object));
984
                }
985
            }
986
        }
987
988
        $template = $this->templateRegistry->getTemplate('acl');
989
990
        return $this->renderWithExtraParams($template, [
991
            'action' => 'acl',
992
            'permissions' => $adminObjectAclData->getUserPermissions(),
993
            'object' => $object,
994
            'users' => $aclUsers,
995
            'roles' => $aclRoles,
996
            'aclUsersForm' => $aclUsersForm->createView(),
997
            'aclRolesForm' => $aclRolesForm->createView(),
998
        ]);
999
    }
1000
1001
    public function getRequest(): ?Request
1002
    {
1003
        return $this->requestStack->getCurrentRequest();
1004
    }
1005
1006
    protected function addFlash(string $type, $message): void
1007
    {
1008
        if ($this->session instanceof Session) {
1009
            $this->session->getFlashBag()->add($type, $message);
1010
        }
1011
    }
1012
1013
    /**
1014
     * @param array<string, mixed> $parameters
1015
     *
1016
     * @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...
1017
     */
1018
    protected function addRenderExtraParams(array $parameters = []): array
1019
    {
1020
        if (!$this->isXmlHttpRequest()) {
1021
            $parameters['breadcrumbs_builder'] = $this->breadcrumbsBuilder;
1022
        }
1023
1024
        $parameters['admin'] = $parameters['admin'] ?? $this->admin;
1025
        $parameters['base_template'] = $parameters['base_template'] ?? $this->getBaseTemplate();
1026
        $parameters['admin_pool'] = $this->pool;
1027
1028
        return $parameters;
1029
    }
1030
1031
    /**
1032
     * Render JSON.
1033
     *
1034
     * @param mixed $data
1035
     * @param int   $status
1036
     * @param array $headers
1037
     *
1038
     * @return JsonResponse with json encoded data
1039
     */
1040
    protected function renderJson($data, $status = Response::HTTP_OK, $headers = [])
1041
    {
1042
        return new JsonResponse($data, $status, $headers);
1043
    }
1044
1045
    /**
1046
     * Returns true if the request is a XMLHttpRequest.
1047
     *
1048
     * @return bool True if the request is an XMLHttpRequest, false otherwise
1049
     */
1050
    protected function isXmlHttpRequest()
1051
    {
1052
        $request = $this->getRequest();
1053
1054
        return $request->isXmlHttpRequest() || $request->get('_xml_http_request');
1055
    }
1056
1057
    /**
1058
     * Returns the correct RESTful verb, given either by the request itself or
1059
     * via the "_method" parameter.
1060
     *
1061
     * @return string HTTP method, either
1062
     */
1063
    protected function getRestMethod()
1064
    {
1065
        $request = $this->getRequest();
1066
1067
        if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) {
1068
            return $request->getMethod();
1069
        }
1070
1071
        return $request->request->get('_method');
1072
    }
1073
1074
    /**
1075
     * Contextualize the admin class depends on the current request.
1076
     *
1077
     * @throws \RuntimeException
1078
     */
1079
    protected function configure(): void
1080
    {
1081
        $request = $this->getRequest();
1082
1083
        $adminCode = $request->get('_sonata_admin');
1084
1085
        if (!$adminCode) {
1086
            throw new \RuntimeException(sprintf(
1087
                'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`',
1088
                static::class,
1089
                $request->get('_route')
1090
            ));
1091
        }
1092
1093
        try {
1094
            $this->admin = $this->pool->getAdminByAdminCode($adminCode);
1095
        } catch (\InvalidArgumentException $e) {
1096
            throw new \RuntimeException(sprintf(
1097
                'Unable to find the admin class related to the current controller (%s)',
1098
                static::class
1099
            ));
1100
        }
1101
1102
        $rootAdmin = $this->admin;
1103
1104
        while ($rootAdmin->isChild()) {
1105
            $rootAdmin->setCurrentChild(true);
1106
            $rootAdmin = $rootAdmin->getParent();
1107
        }
1108
1109
        $rootAdmin->setRequest($request);
1110
1111
        if ($request->get('uniqid')) {
1112
            $this->admin->setUniqid($request->get('uniqid'));
1113
        }
1114
    }
1115
1116
    /**
1117
     * Proxy for the logger service of the container.
1118
     * If no such service is found, a NullLogger is returned.
1119
     */
1120
    protected function getLogger(): LoggerInterface
1121
    {
1122
        return $this->logger;
1123
    }
1124
1125
    /**
1126
     * Returns the base template name.
1127
     *
1128
     * @return string The template name
1129
     */
1130
    protected function getBaseTemplate()
1131
    {
1132
        if ($this->isXmlHttpRequest()) {
1133
            return $this->templateRegistry->getTemplate('ajax');
1134
        }
1135
1136
        return $this->templateRegistry->getTemplate('layout');
1137
    }
1138
1139
    /**
1140
     * @throws \Exception
1141
     */
1142
    protected function handleModelManagerException(\Exception $e): void
1143
    {
1144
        if ($this->kernel->isDebug()) {
1145
            throw $e;
1146
        }
1147
1148
        $context = ['exception' => $e];
1149
        if ($e->getPrevious()) {
1150
            $context['previous_exception_message'] = $e->getPrevious()->getMessage();
1151
        }
1152
        $this->getLogger()->error($e->getMessage(), $context);
1153
    }
1154
1155
    /**
1156
     * Redirect the user depend on this choice.
1157
     *
1158
     * @param object $object
1159
     *
1160
     * @return RedirectResponse
1161
     */
1162
    protected function redirectTo($object)
1163
    {
1164
        $request = $this->getRequest();
1165
1166
        $url = false;
1167
1168
        if (null !== $request->get('btn_update_and_list')) {
1169
            return $this->redirectToList();
1170
        }
1171
        if (null !== $request->get('btn_create_and_list')) {
1172
            return $this->redirectToList();
1173
        }
1174
1175
        if (null !== $request->get('btn_create_and_create')) {
1176
            $params = [];
1177
            if ($this->admin->hasActiveSubClass()) {
1178
                $params['subclass'] = $request->get('subclass');
1179
            }
1180
            $url = $this->admin->generateUrl('create', $params);
1181
        }
1182
1183
        if ('DELETE' === $this->getRestMethod()) {
1184
            return $this->redirectToList();
1185
        }
1186
1187
        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...
1188
            foreach (['edit', 'show'] as $route) {
1189
                if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route, $object)) {
1190
                    $url = $this->admin->generateObjectUrl(
1191
                        $route,
1192
                        $object,
1193
                        $this->getSelectedTab($request)
0 ignored issues
show
Bug introduced by
It seems like $request defined by $this->getRequest() on line 1164 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...
1194
                    );
1195
1196
                    break;
1197
                }
1198
            }
1199
        }
1200
1201
        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...
1202
            return $this->redirectToList();
1203
        }
1204
1205
        return new RedirectResponse($url);
1206
    }
1207
1208
    /**
1209
     * Redirects the user to the list view.
1210
     *
1211
     * @return RedirectResponse
1212
     */
1213
    final protected function redirectToList()
1214
    {
1215
        $parameters = [];
1216
1217
        if ($filter = $this->admin->getFilterParameters()) {
1218
            $parameters['filter'] = $filter;
1219
        }
1220
1221
        return $this->redirect($this->admin->generateUrl('list', $parameters));
1222
    }
1223
1224
    /**
1225
     * Returns true if the preview is requested to be shown.
1226
     *
1227
     * @return bool
1228
     */
1229
    protected function isPreviewRequested()
1230
    {
1231
        $request = $this->getRequest();
1232
1233
        return null !== $request->get('btn_preview');
1234
    }
1235
1236
    /**
1237
     * Returns true if the preview has been approved.
1238
     *
1239
     * @return bool
1240
     */
1241
    protected function isPreviewApproved()
1242
    {
1243
        $request = $this->getRequest();
1244
1245
        return null !== $request->get('btn_preview_approve');
1246
    }
1247
1248
    /**
1249
     * Returns true if the request is in the preview workflow.
1250
     *
1251
     * That means either a preview is requested or the preview has already been shown
1252
     * and it got approved/declined.
1253
     *
1254
     * @return bool
1255
     */
1256
    protected function isInPreviewMode()
1257
    {
1258
        return $this->admin->supportsPreviewMode()
1259
        && ($this->isPreviewRequested()
1260
            || $this->isPreviewApproved()
1261
            || $this->isPreviewDeclined());
1262
    }
1263
1264
    /**
1265
     * Returns true if the preview has been declined.
1266
     *
1267
     * @return bool
1268
     */
1269
    protected function isPreviewDeclined()
1270
    {
1271
        $request = $this->getRequest();
1272
1273
        return null !== $request->get('btn_preview_decline');
1274
    }
1275
1276
    /**
1277
     * Gets ACL users.
1278
     */
1279
    protected function getAclUsers(): \Traversable
1280
    {
1281
        $aclUsers = $this->adminObjectAclUserManager instanceof AdminObjectAclUserManager ?
1282
            $this->adminObjectAclUserManager->findUsers() :
1283
            [];
1284
1285
        return \is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
1286
    }
1287
1288
    /**
1289
     * Gets ACL roles.
1290
     */
1291
    protected function getAclRoles(): \Traversable
1292
    {
1293
        $aclRoles = [];
1294
        $roleHierarchy = $this->pool->getContainer()->getParameter('security.role_hierarchy.roles');
1295
1296
        foreach ($this->pool->getAdminServiceIds() as $id) {
1297
            try {
1298
                $admin = $this->pool->getInstance($id);
1299
            } catch (\Exception $e) {
1300
                continue;
1301
            }
1302
1303
            $baseRole = $admin->getSecurityHandler()->getBaseRole($admin);
1304
            foreach ($admin->getSecurityInformation() as $role => $permissions) {
1305
                $role = sprintf($baseRole, $role);
1306
                $aclRoles[] = $role;
1307
            }
1308
        }
1309
1310
        foreach ($roleHierarchy as $name => $roles) {
1311
            $aclRoles[] = $name;
1312
            $aclRoles = array_merge($aclRoles, $roles);
1313
        }
1314
1315
        $aclRoles = array_unique($aclRoles);
1316
1317
        return \is_array($aclRoles) ? new \ArrayIterator($aclRoles) : $aclRoles;
1318
    }
1319
1320
    /**
1321
     * Validate CSRF token for action without form.
1322
     *
1323
     * @param string $intention
1324
     *
1325
     * @throws HttpException
1326
     */
1327
    protected function validateCsrfToken($intention): void
1328
    {
1329
        $request = $this->getRequest();
1330
        $token = $request->get('_sonata_csrf_token');
1331
1332
        if ($this->csrfTokenManager instanceof CsrfTokenManagerInterface) {
1333
            $valid = $this->csrfTokenManager->isTokenValid(new CsrfToken($intention, $token));
1334
        } else {
1335
            return;
1336
        }
1337
1338
        if (!$valid) {
1339
            throw new HttpException(Response::HTTP_BAD_REQUEST, 'The csrf token is not valid, CSRF attack?');
1340
        }
1341
    }
1342
1343
    /**
1344
     * Escape string for html output.
1345
     *
1346
     * @param string $s
1347
     *
1348
     * @return string
1349
     */
1350
    protected function escapeHtml($s)
1351
    {
1352
        return htmlspecialchars((string) $s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1353
    }
1354
1355
    /**
1356
     * Get CSRF token.
1357
     *
1358
     * @param string $intention
1359
     *
1360
     * @return string|false
1361
     */
1362
    protected function getCsrfToken($intention)
1363
    {
1364
        if ($this->csrfTokenManager instanceof CsrfTokenManagerInterface) {
1365
            return $this->csrfTokenManager->getToken($intention)->getValue();
1366
        }
1367
1368
        return false;
1369
    }
1370
1371
    /**
1372
     * This method can be overloaded in your custom CRUD controller.
1373
     * It's called from createAction.
1374
     *
1375
     * @param object $object
1376
     *
1377
     * @return Response|null
1378
     */
1379
    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...
1380
    {
1381
        return null;
1382
    }
1383
1384
    /**
1385
     * This method can be overloaded in your custom CRUD controller.
1386
     * It's called from editAction.
1387
     *
1388
     * @param object $object
1389
     *
1390
     * @return Response|null
1391
     */
1392
    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...
1393
    {
1394
        return null;
1395
    }
1396
1397
    /**
1398
     * This method can be overloaded in your custom CRUD controller.
1399
     * It's called from deleteAction.
1400
     *
1401
     * @param object $object
1402
     *
1403
     * @return Response|null
1404
     */
1405
    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...
1406
    {
1407
        return null;
1408
    }
1409
1410
    /**
1411
     * This method can be overloaded in your custom CRUD controller.
1412
     * It's called from showAction.
1413
     *
1414
     * @param object $object
1415
     *
1416
     * @return Response|null
1417
     */
1418
    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...
1419
    {
1420
        return null;
1421
    }
1422
1423
    /**
1424
     * This method can be overloaded in your custom CRUD controller.
1425
     * It's called from listAction.
1426
     *
1427
     * @return Response|null
1428
     */
1429
    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...
1430
    {
1431
        return null;
1432
    }
1433
1434
    /**
1435
     * Translate a message id.
1436
     *
1437
     * @param string $id
1438
     * @param string $domain
1439
     * @param string $locale
1440
     *
1441
     * @return string translated string
1442
     */
1443
    final protected function trans($id, array $parameters = [], $domain = null, $locale = null)
1444
    {
1445
        $domain = $domain ?: $this->admin->getTranslationDomain();
1446
1447
        return $this->translator->trans($id, $parameters, $domain, $locale);
1448
    }
1449
1450
    private function getSelectedTab(Request $request): array
1451
    {
1452
        return array_filter(['_tab' => $request->request->get('_tab')]);
1453
    }
1454
1455
    private function checkParentChildAssociation(Request $request, object $object): void
1456
    {
1457
        if (!$this->admin->isChild()) {
1458
            return;
1459
        }
1460
1461
        $parentAdmin = $this->admin->getParent();
1462
        $parentId = $request->get($parentAdmin->getIdParameter());
1463
1464
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
1465
        $propertyPath = new PropertyPath($this->admin->getParentAssociationMapping());
1466
1467
        if ($parentAdmin->getObject($parentId) !== $propertyAccessor->getValue($object, $propertyPath)) {
1468
            throw new \RuntimeException(sprintf(
1469
                'There is no association between "%s" and "%s"',
1470
                $parentAdmin->toString($parentAdmin->getObject($parentId)),
1471
                $this->admin->toString($object)
1472
            ));
1473
        }
1474
    }
1475
1476
    /**
1477
     * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
1478
     */
1479
    private function setFormTheme(FormView $formView, ?array $theme = null): void
1480
    {
1481
        $this->twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme);
1482
    }
1483
1484
    private function handleXmlHttpRequestErrorResponse(Request $request, FormInterface $form): JsonResponse
1485
    {
1486
        if (empty(array_intersect(['application/json', '*/*'], $request->getAcceptableContentTypes()))) {
1487
            return $this->renderJson([], Response::HTTP_NOT_ACCEPTABLE);
1488
        }
1489
1490
        $errors = [];
1491
        foreach ($form->getErrors(true) as $error) {
1492
            $errors[] = $error->getMessage();
1493
        }
1494
1495
        return $this->renderJson([
1496
            'result' => 'error',
1497
            'errors' => $errors,
1498
        ], Response::HTTP_BAD_REQUEST);
1499
    }
1500
1501
    /**
1502
     * @param object $object
1503
     */
1504
    private function handleXmlHttpRequestSuccessResponse(Request $request, $object): JsonResponse
1505
    {
1506
        if (empty(array_intersect(['application/json', '*/*'], $request->getAcceptableContentTypes()))) {
1507
            return $this->renderJson([], Response::HTTP_NOT_ACCEPTABLE);
1508
        }
1509
1510
        return $this->renderJson([
1511
            'result' => 'ok',
1512
            'objectId' => $this->admin->getNormalizedIdentifier($object),
1513
            'objectName' => $this->escapeHtml($this->admin->toString($object)),
1514
        ], Response::HTTP_OK);
1515
    }
1516
}
1517