Completed
Pull Request — master (#6093)
by Mathieu
07:34
created

CRUDController::configure()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 36
rs 9.0328
c 0
b 0
f 0
cc 5
nc 6
nop 0
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\Form\FormInterface;
35
use Symfony\Component\Form\FormRenderer;
36
use Symfony\Component\Form\FormView;
37
use Symfony\Component\HttpFoundation\JsonResponse;
38
use Symfony\Component\HttpFoundation\RedirectResponse;
39
use Symfony\Component\HttpFoundation\Request;
40
use Symfony\Component\HttpFoundation\RequestStack;
41
use Symfony\Component\HttpFoundation\Response;
42
use Symfony\Component\HttpFoundation\Session\Session;
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\PropertyAccess\PropertyAccess;
47
use Symfony\Component\PropertyAccess\PropertyPath;
48
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
49
use Symfony\Component\Security\Csrf\CsrfToken;
50
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
51
use Symfony\Contracts\Translation\TranslatorInterface;
52
use Twig\Environment;
53
54
/**
55
 * @author Thomas Rabaix <[email protected]>
56
 */
57
class CRUDController extends AbstractController
58
{
59
    /**
60
     * The related Admin class.
61
     *
62
     * @var AdminInterface
63
     */
64
    protected $admin;
65
66
    /**
67
     * @var RequestStack
68
     */
69
    private $requestStack;
70
71
    /**
72
     * @var Pool
73
     */
74
    private $pool;
75
76
    /**
77
     * The template registry of the related Admin class.
78
     *
79
     * @var TemplateRegistryInterface
80
     */
81
    private $templateRegistry;
82
83
    /**
84
     * @var BreadcrumbsBuilder
85
     */
86
    private $breadcrumbsBuilder;
87
88
    /**
89
     * @var Environment
90
     */
91
    private $twig;
92
93
    /**
94
     * @var TranslatorInterface
95
     */
96
    private $translator;
97
98
    /**
99
     * @var SessionInterface
100
     */
101
    private $session;
102
103
    /**
104
     * @var bool
105
     */
106
    private $kernelDebug;
107
108
    /**
109
     * @var AuditManagerInterface
110
     */
111
    private $auditManager;
112
113
    /**
114
     * @var Exporter|null
115
     */
116
    private $exporter;
117
118
    /**
119
     * @var AdminExporter|null
120
     */
121
    private $adminExporter;
122
123
    /**
124
     * @var CsrfTokenManagerInterface|null
125
     */
126
    private $csrfTokenManager;
127
128
    /**
129
     * @var LoggerInterface
130
     */
131
    private $logger;
132
133
    /**
134
     * @var AdminObjectAclManipulator|null
135
     */
136
    private $adminObjectAclManipulator;
137
138
    /**
139
     * @var AdminObjectAclUserManager|null
140
     */
141
    private $adminObjectAclUserManager;
142
143
    public function __construct(
144
        RequestStack $requestStack,
145
        Pool $pool,
146
        TemplateRegistryInterface $templateRegistry,
147
        BreadcrumbsBuilder $breadcrumbsBuilder,
148
        Environment $twig,
149
        TranslatorInterface $translator,
150
        SessionInterface $session,
151
        bool $kernelDebug,
152
        AuditManagerInterface $auditManager,
153
        ?Exporter $exporter = null,
154
        ?AdminExporter $adminExporter = null,
155
        ?CsrfTokenManagerInterface $csrfTokenManager = null,
156
        ?LoggerInterface $logger = null,
157
        ?AdminObjectAclManipulator $adminObjectAclManipulator = null,
158
        ?AdminObjectAclUserManager $adminObjectAclUserManager = null
159
    ) {
160
        $this->requestStack = $requestStack;
161
        $this->pool = $pool;
162
        $this->templateRegistry = $templateRegistry;
163
        $this->breadcrumbsBuilder = $breadcrumbsBuilder;
164
        $this->twig = $twig;
165
        $this->translator = $translator;
166
        $this->session = $session;
167
        $this->kernelDebug = $kernelDebug;
168
        $this->auditManager = $auditManager;
169
        $this->exporter = $exporter;
170
        $this->adminExporter = $adminExporter;
171
        $this->csrfTokenManager = $csrfTokenManager;
172
        $this->logger = $logger ?? new NullLogger();
173
        $this->adminObjectAclManipulator = $adminObjectAclManipulator;
174
        $this->adminObjectAclUserManager = $adminObjectAclUserManager;
175
176
        $this->configure();
177
    }
178
179
    /**
180
     * Renders a view while passing mandatory parameters on to the template.
181
     *
182
     * @param string               $view       The view name
183
     * @param array<string, mixed> $parameters An array of parameters to pass to the view
184
     */
185
    public function renderWithExtraParams($view, array $parameters = [], ?Response $response = null): Response
186
    {
187
        $response = $response ?? new Response();
188
189
        return $response->setContent($this->twig->render($view, $this->addRenderExtraParams($parameters)));
190
    }
191
192
    /**
193
     * List action.
194
     *
195
     * @throws AccessDeniedException If access is not granted
196
     *
197
     * @return Response
198
     */
199
    public function listAction(Request $request)
200
    {
201
        $this->admin->checkAccess('list');
202
203
        $preResponse = $this->preList($request);
204
        if (null !== $preResponse) {
205
            return $preResponse;
206
        }
207
208
        if ($listMode = $request->get('_list_mode')) {
209
            $this->admin->setListMode($listMode);
210
        }
211
212
        $datagrid = $this->admin->getDatagrid();
213
        $formView = $datagrid->getForm()->createView();
214
215
        // set the theme for the current Admin Form
216
        $this->setFormTheme($formView, $this->admin->getFilterTheme());
217
218
        $template = $this->templateRegistry->getTemplate('list');
219
220
        return $this->renderWithExtraParams($template, [
221
            'action' => 'list',
222
            'form' => $formView,
223
            'datagrid' => $datagrid,
224
            'csrf_token' => $this->getCsrfToken('sonata.batch'),
225
            'export_formats' => $this->adminExporter ?
226
                $this->adminExporter->getAvailableFormats($this->admin) :
227
                $this->admin->getExportFormats(),
228
        ]);
229
    }
230
231
    /**
232
     * Execute a batch delete.
233
     *
234
     * @throws AccessDeniedException If access is not granted
235
     *
236
     * @return RedirectResponse
237
     */
238
    public function batchActionDelete(ProxyQueryInterface $query)
239
    {
240
        $this->admin->checkAccess('batchDelete');
241
242
        $modelManager = $this->admin->getModelManager();
243
244
        try {
245
            $modelManager->batchDelete($this->admin->getClass(), $query);
246
            $this->addFlash(
247
                'sonata_flash_success',
248
                $this->trans('flash_batch_delete_success', [], 'SonataAdminBundle')
249
            );
250
        } catch (ModelManagerException $e) {
251
            $this->handleModelManagerException($e);
252
            $this->addFlash(
253
                'sonata_flash_error',
254
                $this->trans('flash_batch_delete_error', [], 'SonataAdminBundle')
255
            );
256
        }
257
258
        return $this->redirectToList();
259
    }
260
261
    /**
262
     * Delete action.
263
     *
264
     * @throws NotFoundHttpException If the object does not exist
265
     * @throws AccessDeniedException If access is not granted
266
     *
267
     * @return Response|RedirectResponse
268
     */
269
    public function deleteAction(Request $request)
270
    {
271
        $id = $request->get($this->admin->getIdParameter());
272
        $object = $this->admin->getObject($id);
273
274
        if (!$object) {
275
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
276
        }
277
278
        $this->checkParentChildAssociation($request, $object);
279
280
        $this->admin->checkAccess('delete', $object);
281
282
        $preResponse = $this->preDelete($request, $object);
283
        if (null !== $preResponse) {
284
            return $preResponse;
285
        }
286
287
        if (Request::METHOD_DELETE === $this->getRestMethod()) {
288
            // check the csrf token
289
            $this->validateCsrfToken('sonata.delete');
290
291
            $objectName = $this->admin->toString($object);
292
293
            try {
294
                $this->admin->delete($object);
295
296
                if ($this->isXmlHttpRequest()) {
297
                    return $this->renderJson(['result' => 'ok'], Response::HTTP_OK, []);
298
                }
299
300
                $this->addFlash(
301
                    'sonata_flash_success',
302
                    $this->trans(
303
                        'flash_delete_success',
304
                        ['%name%' => $this->escapeHtml($objectName)],
305
                        'SonataAdminBundle'
306
                    )
307
                );
308
            } catch (ModelManagerException $e) {
309
                $this->handleModelManagerException($e);
310
311
                if ($this->isXmlHttpRequest()) {
312
                    return $this->renderJson(['result' => 'error'], Response::HTTP_OK, []);
313
                }
314
315
                $this->addFlash(
316
                    'sonata_flash_error',
317
                    $this->trans(
318
                        'flash_delete_error',
319
                        ['%name%' => $this->escapeHtml($objectName)],
320
                        'SonataAdminBundle'
321
                    )
322
                );
323
            }
324
325
            return $this->redirectTo($object);
326
        }
327
328
        $template = $this->templateRegistry->getTemplate('delete');
329
330
        return $this->renderWithExtraParams($template, [
331
            'object' => $object,
332
            'action' => 'delete',
333
            'csrf_token' => $this->getCsrfToken('sonata.delete'),
334
        ]);
335
    }
336
337
    /**
338
     * Edit action.
339
     *
340
     * @throws NotFoundHttpException If the object does not exist
341
     * @throws AccessDeniedException If access is not granted
342
     *
343
     * @return Response|RedirectResponse
344
     */
345
    public function editAction(Request $request)
346
    {
347
        // the key used to lookup the template
348
        $templateKey = 'edit';
349
350
        $id = $request->get($this->admin->getIdParameter());
351
        $existingObject = $this->admin->getObject($id);
352
353
        if (!$existingObject) {
354
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
355
        }
356
357
        $this->checkParentChildAssociation($request, $existingObject);
358
359
        $this->admin->checkAccess('edit', $existingObject);
360
361
        $preResponse = $this->preEdit($request, $existingObject);
362
        if (null !== $preResponse) {
363
            return $preResponse;
364
        }
365
366
        $this->admin->setSubject($existingObject);
367
        $objectId = $this->admin->getNormalizedIdentifier($existingObject);
368
369
        $form = $this->admin->getForm();
370
371
        $form->setData($existingObject);
372
        $form->handleRequest($request);
373
374
        if ($form->isSubmitted()) {
375
            $isFormValid = $form->isValid();
376
377
            // persist if the form was valid and if in preview mode the preview was approved
378
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
379
                $submittedObject = $form->getData();
380
                $this->admin->setSubject($submittedObject);
381
382
                try {
383
                    $existingObject = $this->admin->update($submittedObject);
384
385
                    if ($this->isXmlHttpRequest()) {
386
                        return $this->handleXmlHttpRequestSuccessResponse($request, $existingObject);
387
                    }
388
389
                    $this->addFlash(
390
                        'sonata_flash_success',
391
                        $this->trans(
392
                            'flash_edit_success',
393
                            ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
394
                            'SonataAdminBundle'
395
                        )
396
                    );
397
398
                    // redirect to edit mode
399
                    return $this->redirectTo($existingObject);
400
                } catch (ModelManagerException $e) {
401
                    $this->handleModelManagerException($e);
402
403
                    $isFormValid = false;
404
                } catch (LockException $e) {
405
                    $this->addFlash('sonata_flash_error', $this->trans('flash_lock_error', [
406
                        '%name%' => $this->escapeHtml($this->admin->toString($existingObject)),
407
                        '%link_start%' => sprintf('<a href="%s">', $this->admin->generateObjectUrl('edit', $existingObject)),
408
                        '%link_end%' => '</a>',
409
                    ], 'SonataAdminBundle'));
410
                }
411
            }
412
413
            // show an error message if the form failed validation
414
            if (!$isFormValid) {
415
                if ($this->isXmlHttpRequest() && null !== ($response = $this->handleXmlHttpRequestErrorResponse($request, $form))) {
416
                    return $response;
417
                }
418
419
                $this->addFlash(
420
                    'sonata_flash_error',
421
                    $this->trans(
422
                        'flash_edit_error',
423
                        ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
424
                        'SonataAdminBundle'
425
                    )
426
                );
427
            } elseif ($this->isPreviewRequested()) {
428
                // enable the preview template if the form was valid and preview was requested
429
                $templateKey = 'preview';
430
                $this->admin->getShow();
431
            }
432
        }
433
434
        $formView = $form->createView();
435
        // set the theme for the current Admin Form
436
        $this->setFormTheme($formView, $this->admin->getFormTheme());
437
438
        $template = $this->templateRegistry->getTemplate($templateKey);
439
440
        return $this->renderWithExtraParams($template, [
441
            'action' => 'edit',
442
            'form' => $formView,
443
            'object' => $existingObject,
444
            'objectId' => $objectId,
445
        ]);
446
    }
447
448
    /**
449
     * Batch action.
450
     *
451
     * @throws NotFoundHttpException If the HTTP method is not POST
452
     * @throws \RuntimeException     If the batch action is not defined
453
     *
454
     * @return Response|RedirectResponse
455
     */
456
    public function batchAction(Request $request)
457
    {
458
        $restMethod = $this->getRestMethod();
459
460
        if (Request::METHOD_POST !== $restMethod) {
461
            throw $this->createNotFoundException(sprintf(
462
                'Invalid request method given "%s", %s expected',
463
                $restMethod,
464
                Request::METHOD_POST
465
            ));
466
        }
467
468
        // check the csrf token
469
        $this->validateCsrfToken('sonata.batch');
470
471
        $confirmation = $request->get('confirmation', false);
472
473
        if ($data = json_decode($request->get('data', ''), true)) {
474
            $action = $data['action'];
475
            $idx = $data['idx'];
476
            $allElements = (bool) $data['all_elements'];
477
            $request->request->replace(array_merge($request->request->all(), $data));
478
        } else {
479
            $action = $request->request->getAlnum('action');
480
            $idx = $request->request->get('idx', []);
0 ignored issues
show
Documentation introduced by
array() is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
481
            $allElements = $request->request->getBoolean('all_elements');
482
            $data = $request->request->all();
483
484
            unset($data['_sonata_csrf_token']);
485
        }
486
487
        $batchActions = $this->admin->getBatchActions();
488
        if (!\array_key_exists($action, $batchActions)) {
489
            throw new \RuntimeException(sprintf('The `%s` batch action is not defined', $action));
490
        }
491
492
        $camelizedAction = InflectorFactory::create()->build()->classify($action);
493
        $isRelevantAction = sprintf('batchAction%sIsRelevant', $camelizedAction);
494
495
        if (method_exists($this, $isRelevantAction)) {
496
            $nonRelevantMessage = $this->$isRelevantAction($idx, $allElements, $request);
497
        } else {
498
            $nonRelevantMessage = 0 !== \count($idx) || $allElements; // at least one item is selected
499
        }
500
501
        if (!$nonRelevantMessage) { // default non relevant message (if false of null)
502
            $nonRelevantMessage = 'flash_batch_empty';
503
        }
504
505
        $datagrid = $this->admin->getDatagrid();
506
        $datagrid->buildPager();
507
508
        if (true !== $nonRelevantMessage) {
509
            $this->addFlash(
510
                'sonata_flash_info',
511
                $this->trans($nonRelevantMessage, [], 'SonataAdminBundle')
512
            );
513
514
            return $this->redirectToList();
515
        }
516
517
        $askConfirmation = $batchActions[$action]['ask_confirmation'] ??
518
            true;
519
520
        if ($askConfirmation && 'ok' !== $confirmation) {
521
            $actionLabel = $batchActions[$action]['label'];
522
            $batchTranslationDomain = $batchActions[$action]['translation_domain'] ??
523
                $this->admin->getTranslationDomain();
524
525
            $formView = $datagrid->getForm()->createView();
526
            $this->setFormTheme($formView, $this->admin->getFilterTheme());
527
528
            $template = !empty($batchActions[$action]['template']) ?
529
                $batchActions[$action]['template'] :
530
                $this->templateRegistry->getTemplate('batch_confirmation');
531
532
            return $this->renderWithExtraParams($template, [
533
                'action' => 'list',
534
                'action_label' => $actionLabel,
535
                'batch_translation_domain' => $batchTranslationDomain,
536
                'datagrid' => $datagrid,
537
                'form' => $formView,
538
                'data' => $data,
539
                'csrf_token' => $this->getCsrfToken('sonata.batch'),
540
            ]);
541
        }
542
543
        // execute the action, batchActionXxxxx
544
        $finalAction = sprintf('batchAction%s', $camelizedAction);
545
        if (!method_exists($this, $finalAction)) {
546
            throw new \RuntimeException(sprintf('A `%s::%s` method must be callable', static::class, $finalAction));
547
        }
548
549
        $query = $datagrid->getQuery();
550
551
        $query->setFirstResult(null);
552
        $query->setMaxResults(null);
553
554
        $this->admin->preBatchAction($action, $query, $idx, $allElements);
555
556
        if (\count($idx) > 0) {
557
            $this->admin->getModelManager()->addIdentifiersToQuery($this->admin->getClass(), $query, $idx);
558
        } elseif (!$allElements) {
559
            $this->addFlash(
560
                'sonata_flash_info',
561
                $this->trans('flash_batch_no_elements_processed', [], 'SonataAdminBundle')
562
            );
563
564
            return $this->redirectToList();
565
        }
566
567
        return $this->$finalAction($query, $request);
568
    }
569
570
    /**
571
     * Create action.
572
     *
573
     * @throws AccessDeniedException If access is not granted
574
     *
575
     * @return Response
576
     */
577
    public function createAction(Request $request)
578
    {
579
        // the key used to lookup the template
580
        $templateKey = 'edit';
581
582
        $this->admin->checkAccess('create');
583
584
        $class = new \ReflectionClass($this->admin->hasActiveSubClass() ? $this->admin->getActiveSubClass() : $this->admin->getClass());
585
586
        if ($class->isAbstract()) {
587
            return $this->renderWithExtraParams(
588
                '@SonataAdmin/CRUD/select_subclass.html.twig',
589
                [
590
                    'base_template' => $this->getBaseTemplate(),
591
                    'admin' => $this->admin,
592
                    'action' => 'create',
593
                ]
594
            );
595
        }
596
597
        $newObject = $this->admin->getNewInstance();
598
599
        $preResponse = $this->preCreate($request, $newObject);
600
        if (null !== $preResponse) {
601
            return $preResponse;
602
        }
603
604
        $this->admin->setSubject($newObject);
605
606
        $form = $this->admin->getForm();
607
608
        $form->setData($newObject);
609
        $form->handleRequest($request);
610
611
        if ($form->isSubmitted()) {
612
            $isFormValid = $form->isValid();
613
614
            // persist if the form was valid and if in preview mode the preview was approved
615
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
616
                $submittedObject = $form->getData();
617
                $this->admin->setSubject($submittedObject);
618
                $this->admin->checkAccess('create', $submittedObject);
619
620
                try {
621
                    $newObject = $this->admin->create($submittedObject);
622
623
                    if ($this->isXmlHttpRequest()) {
624
                        return $this->handleXmlHttpRequestSuccessResponse($request, $newObject);
625
                    }
626
627
                    $this->addFlash(
628
                        'sonata_flash_success',
629
                        $this->trans(
630
                            'flash_create_success',
631
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
632
                            'SonataAdminBundle'
633
                        )
634
                    );
635
636
                    // redirect to edit mode
637
                    return $this->redirectTo($newObject);
638
                } catch (ModelManagerException $e) {
639
                    $this->handleModelManagerException($e);
640
641
                    $isFormValid = false;
642
                }
643
            }
644
645
            // show an error message if the form failed validation
646
            if (!$isFormValid) {
647
                if ($this->isXmlHttpRequest() && null !== ($response = $this->handleXmlHttpRequestErrorResponse($request, $form))) {
648
                    return $response;
649
                }
650
651
                $this->addFlash(
652
                    'sonata_flash_error',
653
                    $this->trans(
654
                        'flash_create_error',
655
                        ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
656
                        'SonataAdminBundle'
657
                    )
658
                );
659
            } elseif ($this->isPreviewRequested()) {
660
                // pick the preview template if the form was valid and preview was requested
661
                $templateKey = 'preview';
662
                $this->admin->getShow();
663
            }
664
        }
665
666
        $formView = $form->createView();
667
        // set the theme for the current Admin Form
668
        $this->setFormTheme($formView, $this->admin->getFormTheme());
669
670
        $template = $this->templateRegistry->getTemplate($templateKey);
671
672
        return $this->renderWithExtraParams($template, [
673
            'action' => 'create',
674
            'form' => $formView,
675
            'object' => $newObject,
676
            'objectId' => null,
677
        ]);
678
    }
679
680
    /**
681
     * Show action.
682
     *
683
     * @throws NotFoundHttpException If the object does not exist
684
     * @throws AccessDeniedException If access is not granted
685
     *
686
     * @return Response
687
     */
688
    public function showAction(Request $request)
689
    {
690
        $id = $request->get($this->admin->getIdParameter());
691
        $object = $this->admin->getObject($id);
692
693
        if (!$object) {
694
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
695
        }
696
697
        $this->checkParentChildAssociation($request, $object);
698
699
        $this->admin->checkAccess('show', $object);
700
701
        $preResponse = $this->preShow($request, $object);
702
        if (null !== $preResponse) {
703
            return $preResponse;
704
        }
705
706
        $this->admin->setSubject($object);
707
708
        $fields = $this->admin->getShow();
709
        \assert($fields instanceof FieldDescriptionCollection);
710
711
        $template = $this->templateRegistry->getTemplate('show');
712
713
        return $this->renderWithExtraParams($template, [
714
            'action' => 'show',
715
            'object' => $object,
716
            'elements' => $fields,
717
        ]);
718
    }
719
720
    /**
721
     * Show history revisions for object.
722
     *
723
     * @throws AccessDeniedException If access is not granted
724
     * @throws NotFoundHttpException If the object does not exist or the audit reader is not available
725
     *
726
     * @return Response
727
     */
728
    public function historyAction(Request $request)
729
    {
730
        $id = $request->get($this->admin->getIdParameter());
731
        $object = $this->admin->getObject($id);
732
733
        if (!$object) {
734
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
735
        }
736
737
        $this->admin->checkAccess('history', $object);
738
739
        if (!$this->auditManager->hasReader($this->admin->getClass())) {
740
            throw $this->createNotFoundException(
741
                sprintf(
742
                    'unable to find the audit reader for class : %s',
743
                    $this->admin->getClass()
744
                )
745
            );
746
        }
747
748
        $reader = $this->auditManager->getReader($this->admin->getClass());
749
750
        $revisions = $reader->findRevisions($this->admin->getClass(), $id);
751
752
        $template = $this->templateRegistry->getTemplate('history');
753
754
        return $this->renderWithExtraParams($template, [
755
            'action' => 'history',
756
            'object' => $object,
757
            'revisions' => $revisions,
758
            'currentRevision' => $revisions ? current($revisions) : false,
759
        ]);
760
    }
761
762
    /**
763
     * View history revision of object.
764
     *
765
     * @param string|null $revision
766
     *
767
     * @throws AccessDeniedException If access is not granted
768
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
769
     *
770
     * @return Response
771
     */
772
    public function historyViewRevisionAction(Request $request, $revision = null)
773
    {
774
        $id = $request->get($this->admin->getIdParameter());
775
        $object = $this->admin->getObject($id);
776
777
        if (!$object) {
778
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
779
        }
780
781
        $this->admin->checkAccess('historyViewRevision', $object);
782
783
        if (!$this->auditManager->hasReader($this->admin->getClass())) {
784
            throw $this->createNotFoundException(
785
                sprintf(
786
                    'unable to find the audit reader for class : %s',
787
                    $this->admin->getClass()
788
                )
789
            );
790
        }
791
792
        $reader = $this->auditManager->getReader($this->admin->getClass());
793
794
        // retrieve the revisioned object
795
        $object = $reader->find($this->admin->getClass(), $id, $revision);
796
797
        if (!$object) {
798
            throw $this->createNotFoundException(sprintf(
799
                'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
800
                $id,
801
                $revision,
802
                $this->admin->getClass()
803
            ));
804
        }
805
806
        $this->admin->setSubject($object);
807
808
        $template = $this->templateRegistry->getTemplate('show');
809
810
        return $this->renderWithExtraParams($template, [
811
            'action' => 'show',
812
            'object' => $object,
813
            'elements' => $this->admin->getShow(),
814
        ]);
815
    }
816
817
    /**
818
     * Compare history revisions of object.
819
     *
820
     * @param int|string|null $baseRevision
821
     * @param int|string|null $compareRevision
822
     *
823
     * @throws AccessDeniedException If access is not granted
824
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
825
     *
826
     * @return Response
827
     */
828
    public function historyCompareRevisionsAction(Request $request, $baseRevision = null, $compareRevision = null)
829
    {
830
        $this->admin->checkAccess('historyCompareRevisions');
831
832
        $id = $request->get($this->admin->getIdParameter());
833
        $object = $this->admin->getObject($id);
834
835
        if (!$object) {
836
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
837
        }
838
839
        if (!$this->auditManager->hasReader($this->admin->getClass())) {
840
            throw $this->createNotFoundException(
841
                sprintf(
842
                    'unable to find the audit reader for class : %s',
843
                    $this->admin->getClass()
844
                )
845
            );
846
        }
847
848
        $reader = $this->auditManager->getReader($this->admin->getClass());
849
850
        // retrieve the base revision
851
        $baseObject = $reader->find($this->admin->getClass(), $id, $baseRevision);
852
        if (!$baseObject) {
853
            throw $this->createNotFoundException(
854
                sprintf(
855
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
856
                    $id,
857
                    $baseRevision,
858
                    $this->admin->getClass()
859
                )
860
            );
861
        }
862
863
        // retrieve the compare revision
864
        $compareObject = $reader->find($this->admin->getClass(), $id, $compareRevision);
865
        if (!$compareObject) {
866
            throw $this->createNotFoundException(
867
                sprintf(
868
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
869
                    $id,
870
                    $compareRevision,
871
                    $this->admin->getClass()
872
                )
873
            );
874
        }
875
876
        $this->admin->setSubject($baseObject);
877
878
        $template = $this->templateRegistry->getTemplate('show_compare');
879
880
        return $this->renderWithExtraParams($template, [
881
            'action' => 'show',
882
            'object' => $baseObject,
883
            'object_compare' => $compareObject,
884
            'elements' => $this->admin->getShow(),
885
        ]);
886
    }
887
888
    /**
889
     * Export data to specified format.
890
     *
891
     * @throws AccessDeniedException If access is not granted
892
     * @throws \RuntimeException     If the export format is invalid
893
     *
894
     * @return Response
895
     */
896
    public function exportAction(Request $request)
897
    {
898
        $this->admin->checkAccess('export');
899
900
        $format = $request->get('format');
901
902
        $allowedExportFormats = $this->adminExporter->getAvailableFormats($this->admin);
903
        $filename = $this->adminExporter->getExportFilename($this->admin, $format);
904
905
        if (!\in_array($format, $allowedExportFormats, true)) {
906
            throw new \RuntimeException(sprintf(
907
                'Export in format `%s` is not allowed for class: `%s`. Allowed formats are: `%s`',
908
                $format,
909
                $this->admin->getClass(),
910
                implode(', ', $allowedExportFormats)
911
            ));
912
        }
913
914
        return $this->exporter->getResponse(
915
            $format,
916
            $filename,
917
            $this->admin->getDataSourceIterator()
918
        );
919
    }
920
921
    /**
922
     * Returns the Response object associated to the acl action.
923
     *
924
     * @throws AccessDeniedException If access is not granted
925
     * @throws NotFoundHttpException If the object does not exist or the ACL is not enabled
926
     *
927
     * @return Response|RedirectResponse
928
     */
929
    public function aclAction(Request $request)
930
    {
931
        if (!$this->admin->isAclEnabled()) {
932
            throw $this->createNotFoundException('ACL are not enabled for this admin');
933
        }
934
935
        $id = $request->get($this->admin->getIdParameter());
936
        $object = $this->admin->getObject($id);
937
938
        if (!$object) {
939
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
940
        }
941
942
        $this->admin->checkAccess('acl', $object);
943
944
        $this->admin->setSubject($object);
945
        $aclUsers = $this->getAclUsers();
946
        $aclRoles = $this->getAclRoles();
947
948
        $adminObjectAclData = new AdminObjectAclData(
949
            $this->admin,
950
            $object,
951
            $aclUsers,
952
            $this->adminObjectAclManipulator->getMaskBuilderClass(),
953
            $aclRoles
954
        );
955
956
        $aclUsersForm = $this->adminObjectAclManipulator->createAclUsersForm($adminObjectAclData);
957
        $aclRolesForm = $this->adminObjectAclManipulator->createAclRolesForm($adminObjectAclData);
958
959
        if (Request::METHOD_POST === $request->getMethod()) {
960
            if ($request->request->has(AdminObjectAclManipulator::ACL_USERS_FORM_NAME)) {
961
                $form = $aclUsersForm;
962
                $updateMethod = 'updateAclUsers';
963
            } elseif ($request->request->has(AdminObjectAclManipulator::ACL_ROLES_FORM_NAME)) {
964
                $form = $aclRolesForm;
965
                $updateMethod = 'updateAclRoles';
966
            }
967
968
            if (isset($form, $updateMethod)) {
969
                $form->handleRequest($request);
970
971
                if ($form->isValid()) {
972
                    $this->adminObjectAclManipulator->$updateMethod($adminObjectAclData);
973
                    $this->addFlash(
974
                        'sonata_flash_success',
975
                        $this->trans('flash_acl_edit_success', [], 'SonataAdminBundle')
976
                    );
977
978
                    return new RedirectResponse($this->admin->generateObjectUrl('acl', $object));
979
                }
980
            }
981
        }
982
983
        $template = $this->templateRegistry->getTemplate('acl');
984
985
        return $this->renderWithExtraParams($template, [
986
            'action' => 'acl',
987
            'permissions' => $adminObjectAclData->getUserPermissions(),
988
            'object' => $object,
989
            'users' => $aclUsers,
990
            'roles' => $aclRoles,
991
            'aclUsersForm' => $aclUsersForm->createView(),
992
            'aclRolesForm' => $aclRolesForm->createView(),
993
        ]);
994
    }
995
996
    public function getRequest(): ?Request
997
    {
998
        return $this->requestStack->getCurrentRequest();
999
    }
1000
1001
    protected function addFlash(string $type, $message): void
1002
    {
1003
        if ($this->session instanceof Session) {
1004
            $this->session->getFlashBag()->add($type, $message);
1005
        }
1006
    }
1007
1008
    /**
1009
     * @param array<string, mixed> $parameters
1010
     *
1011
     * @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...
1012
     */
1013
    protected function addRenderExtraParams(array $parameters = []): array
1014
    {
1015
        if (!$this->isXmlHttpRequest()) {
1016
            $parameters['breadcrumbs_builder'] = $this->breadcrumbsBuilder;
1017
        }
1018
1019
        $parameters['admin'] = $parameters['admin'] ?? $this->admin;
1020
        $parameters['base_template'] = $parameters['base_template'] ?? $this->getBaseTemplate();
1021
        $parameters['admin_pool'] = $this->pool;
1022
1023
        return $parameters;
1024
    }
1025
1026
    /**
1027
     * Render JSON.
1028
     *
1029
     * @param mixed $data
1030
     * @param int   $status
1031
     * @param array $headers
1032
     *
1033
     * @return JsonResponse with json encoded data
1034
     */
1035
    protected function renderJson($data, $status = Response::HTTP_OK, $headers = [])
1036
    {
1037
        return new JsonResponse($data, $status, $headers);
1038
    }
1039
1040
    /**
1041
     * Returns true if the request is a XMLHttpRequest.
1042
     *
1043
     * @return bool True if the request is an XMLHttpRequest, false otherwise
1044
     */
1045
    protected function isXmlHttpRequest()
1046
    {
1047
        $request = $this->getRequest();
1048
1049
        return $request->isXmlHttpRequest() || $request->get('_xml_http_request');
1050
    }
1051
1052
    /**
1053
     * Returns the correct RESTful verb, given either by the request itself or
1054
     * via the "_method" parameter.
1055
     *
1056
     * @return string HTTP method, either
1057
     */
1058
    protected function getRestMethod()
1059
    {
1060
        $request = $this->getRequest();
1061
1062
        if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) {
1063
            return $request->getMethod();
1064
        }
1065
1066
        return $request->request->get('_method');
1067
    }
1068
1069
    /**
1070
     * Contextualize the admin class depends on the current request.
1071
     *
1072
     * @throws \RuntimeException
1073
     */
1074
    protected function configure(): void
1075
    {
1076
        $request = $this->getRequest();
1077
1078
        $adminCode = $request->get('_sonata_admin');
1079
1080
        if (!$adminCode) {
1081
            throw new \RuntimeException(sprintf(
1082
                'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`',
1083
                static::class,
1084
                $request->get('_route')
1085
            ));
1086
        }
1087
1088
        try {
1089
            $this->admin = $this->pool->getAdminByAdminCode($adminCode);
1090
        } catch (\InvalidArgumentException $e) {
1091
            throw new \RuntimeException(sprintf(
1092
                'Unable to find the admin class related to the current controller (%s)',
1093
                static::class
1094
            ));
1095
        }
1096
1097
        $rootAdmin = $this->admin;
1098
1099
        while ($rootAdmin->isChild()) {
1100
            $rootAdmin->setCurrentChild(true);
1101
            $rootAdmin = $rootAdmin->getParent();
1102
        }
1103
1104
        $rootAdmin->setRequest($request);
1105
1106
        if ($request->get('uniqid')) {
1107
            $this->admin->setUniqid($request->get('uniqid'));
1108
        }
1109
    }
1110
1111
    /**
1112
     * Proxy for the logger service of the container.
1113
     * If no such service is found, a NullLogger is returned.
1114
     */
1115
    protected function getLogger(): LoggerInterface
1116
    {
1117
        return $this->logger;
1118
    }
1119
1120
    /**
1121
     * Returns the base template name.
1122
     *
1123
     * @return string The template name
1124
     */
1125
    protected function getBaseTemplate()
1126
    {
1127
        if ($this->isXmlHttpRequest()) {
1128
            return $this->templateRegistry->getTemplate('ajax');
1129
        }
1130
1131
        return $this->templateRegistry->getTemplate('layout');
1132
    }
1133
1134
    /**
1135
     * @throws \Exception
1136
     */
1137
    protected function handleModelManagerException(\Exception $e): void
1138
    {
1139
        if ($this->kernelDebug) {
1140
            throw $e;
1141
        }
1142
1143
        $context = ['exception' => $e];
1144
        if ($e->getPrevious()) {
1145
            $context['previous_exception_message'] = $e->getPrevious()->getMessage();
1146
        }
1147
        $this->getLogger()->error($e->getMessage(), $context);
1148
    }
1149
1150
    /**
1151
     * Redirect the user depend on this choice.
1152
     *
1153
     * @param object $object
1154
     *
1155
     * @return RedirectResponse
1156
     */
1157
    protected function redirectTo($object)
1158
    {
1159
        $request = $this->getRequest();
1160
1161
        $url = false;
1162
1163
        if (null !== $request->get('btn_update_and_list')) {
1164
            return $this->redirectToList();
1165
        }
1166
        if (null !== $request->get('btn_create_and_list')) {
1167
            return $this->redirectToList();
1168
        }
1169
1170
        if (null !== $request->get('btn_create_and_create')) {
1171
            $params = [];
1172
            if ($this->admin->hasActiveSubClass()) {
1173
                $params['subclass'] = $request->get('subclass');
1174
            }
1175
            $url = $this->admin->generateUrl('create', $params);
1176
        }
1177
1178
        if ('DELETE' === $this->getRestMethod()) {
1179
            return $this->redirectToList();
1180
        }
1181
1182
        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...
1183
            foreach (['edit', 'show'] as $route) {
1184
                if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route, $object)) {
1185
                    $url = $this->admin->generateObjectUrl(
1186
                        $route,
1187
                        $object,
1188
                        $this->getSelectedTab($request)
0 ignored issues
show
Bug introduced by
It seems like $request defined by $this->getRequest() on line 1159 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...
1189
                    );
1190
1191
                    break;
1192
                }
1193
            }
1194
        }
1195
1196
        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...
1197
            return $this->redirectToList();
1198
        }
1199
1200
        return new RedirectResponse($url);
1201
    }
1202
1203
    /**
1204
     * Redirects the user to the list view.
1205
     *
1206
     * @return RedirectResponse
1207
     */
1208
    final protected function redirectToList()
1209
    {
1210
        $parameters = [];
1211
1212
        if ($filter = $this->admin->getFilterParameters()) {
1213
            $parameters['filter'] = $filter;
1214
        }
1215
1216
        return $this->redirect($this->admin->generateUrl('list', $parameters));
1217
    }
1218
1219
    /**
1220
     * Returns true if the preview is requested to be shown.
1221
     *
1222
     * @return bool
1223
     */
1224
    protected function isPreviewRequested()
1225
    {
1226
        $request = $this->getRequest();
1227
1228
        return null !== $request->get('btn_preview');
1229
    }
1230
1231
    /**
1232
     * Returns true if the preview has been approved.
1233
     *
1234
     * @return bool
1235
     */
1236
    protected function isPreviewApproved()
1237
    {
1238
        $request = $this->getRequest();
1239
1240
        return null !== $request->get('btn_preview_approve');
1241
    }
1242
1243
    /**
1244
     * Returns true if the request is in the preview workflow.
1245
     *
1246
     * That means either a preview is requested or the preview has already been shown
1247
     * and it got approved/declined.
1248
     *
1249
     * @return bool
1250
     */
1251
    protected function isInPreviewMode()
1252
    {
1253
        return $this->admin->supportsPreviewMode()
1254
        && ($this->isPreviewRequested()
1255
            || $this->isPreviewApproved()
1256
            || $this->isPreviewDeclined());
1257
    }
1258
1259
    /**
1260
     * Returns true if the preview has been declined.
1261
     *
1262
     * @return bool
1263
     */
1264
    protected function isPreviewDeclined()
1265
    {
1266
        $request = $this->getRequest();
1267
1268
        return null !== $request->get('btn_preview_decline');
1269
    }
1270
1271
    /**
1272
     * Gets ACL users.
1273
     */
1274
    protected function getAclUsers(): \Traversable
1275
    {
1276
        $aclUsers = $this->adminObjectAclUserManager instanceof AdminObjectAclUserManager ?
1277
            $this->adminObjectAclUserManager->findUsers() :
1278
            [];
1279
1280
        return \is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
1281
    }
1282
1283
    /**
1284
     * Gets ACL roles.
1285
     */
1286
    protected function getAclRoles(): \Traversable
1287
    {
1288
        $aclRoles = [];
1289
        $roleHierarchy = $this->pool->getContainer()->getParameter('security.role_hierarchy.roles');
1290
1291
        foreach ($this->pool->getAdminServiceIds() as $id) {
1292
            try {
1293
                $admin = $this->pool->getInstance($id);
1294
            } catch (\Exception $e) {
1295
                continue;
1296
            }
1297
1298
            $baseRole = $admin->getSecurityHandler()->getBaseRole($admin);
1299
            foreach ($admin->getSecurityInformation() as $role => $permissions) {
1300
                $role = sprintf($baseRole, $role);
1301
                $aclRoles[] = $role;
1302
            }
1303
        }
1304
1305
        foreach ($roleHierarchy as $name => $roles) {
1306
            $aclRoles[] = $name;
1307
            $aclRoles = array_merge($aclRoles, $roles);
1308
        }
1309
1310
        $aclRoles = array_unique($aclRoles);
1311
1312
        return new \ArrayIterator($aclRoles);
1313
    }
1314
1315
    /**
1316
     * Validate CSRF token for action without form.
1317
     *
1318
     * @param string $intention
1319
     *
1320
     * @throws HttpException
1321
     */
1322
    protected function validateCsrfToken($intention): void
1323
    {
1324
        $request = $this->getRequest();
1325
        $token = $request->get('_sonata_csrf_token');
1326
1327
        if ($this->csrfTokenManager instanceof CsrfTokenManagerInterface) {
1328
            $valid = $this->csrfTokenManager->isTokenValid(new CsrfToken($intention, $token));
1329
        } else {
1330
            return;
1331
        }
1332
1333
        if (!$valid) {
1334
            throw new HttpException(Response::HTTP_BAD_REQUEST, 'The csrf token is not valid, CSRF attack?');
1335
        }
1336
    }
1337
1338
    /**
1339
     * Escape string for html output.
1340
     *
1341
     * @param string $s
1342
     *
1343
     * @return string
1344
     */
1345
    protected function escapeHtml($s)
1346
    {
1347
        return htmlspecialchars((string) $s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1348
    }
1349
1350
    /**
1351
     * Get CSRF token.
1352
     *
1353
     * @param string $intention
1354
     *
1355
     * @return string|false
1356
     */
1357
    protected function getCsrfToken($intention)
1358
    {
1359
        if ($this->csrfTokenManager) {
1360
            return $this->csrfTokenManager->getToken($intention)->getValue();
1361
        }
1362
1363
        return false;
1364
    }
1365
1366
    /**
1367
     * This method can be overloaded in your custom CRUD controller.
1368
     * It's called from createAction.
1369
     *
1370
     * @param object $object
1371
     *
1372
     * @return Response|null
1373
     */
1374
    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...
1375
    {
1376
        return null;
1377
    }
1378
1379
    /**
1380
     * This method can be overloaded in your custom CRUD controller.
1381
     * It's called from editAction.
1382
     *
1383
     * @param object $object
1384
     *
1385
     * @return Response|null
1386
     */
1387
    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...
1388
    {
1389
        return null;
1390
    }
1391
1392
    /**
1393
     * This method can be overloaded in your custom CRUD controller.
1394
     * It's called from deleteAction.
1395
     *
1396
     * @param object $object
1397
     *
1398
     * @return Response|null
1399
     */
1400
    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...
1401
    {
1402
        return null;
1403
    }
1404
1405
    /**
1406
     * This method can be overloaded in your custom CRUD controller.
1407
     * It's called from showAction.
1408
     *
1409
     * @param object $object
1410
     *
1411
     * @return Response|null
1412
     */
1413
    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...
1414
    {
1415
        return null;
1416
    }
1417
1418
    /**
1419
     * This method can be overloaded in your custom CRUD controller.
1420
     * It's called from listAction.
1421
     *
1422
     * @return Response|null
1423
     */
1424
    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...
1425
    {
1426
        return null;
1427
    }
1428
1429
    /**
1430
     * Translate a message id.
1431
     *
1432
     * @param string $id
1433
     * @param string $domain
1434
     * @param string $locale
1435
     *
1436
     * @return string translated string
1437
     */
1438
    final protected function trans($id, array $parameters = [], $domain = null, $locale = null)
1439
    {
1440
        $domain = $domain ?: $this->admin->getTranslationDomain();
1441
1442
        return $this->translator->trans($id, $parameters, $domain, $locale);
1443
    }
1444
1445
    private function getSelectedTab(Request $request): array
1446
    {
1447
        return array_filter(['_tab' => $request->request->get('_tab')]);
1448
    }
1449
1450
    private function checkParentChildAssociation(Request $request, object $object): void
1451
    {
1452
        if (!$this->admin->isChild()) {
1453
            return;
1454
        }
1455
1456
        $parentAdmin = $this->admin->getParent();
1457
        $parentId = $request->get($parentAdmin->getIdParameter());
1458
1459
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
1460
        $propertyPath = new PropertyPath($this->admin->getParentAssociationMapping());
1461
1462
        if ($parentAdmin->getObject($parentId) !== $propertyAccessor->getValue($object, $propertyPath)) {
1463
            throw new \RuntimeException(sprintf(
1464
                'There is no association between "%s" and "%s"',
1465
                $parentAdmin->toString($parentAdmin->getObject($parentId)),
1466
                $this->admin->toString($object)
1467
            ));
1468
        }
1469
    }
1470
1471
    /**
1472
     * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
1473
     */
1474
    private function setFormTheme(FormView $formView, ?array $theme = null): void
1475
    {
1476
        $this->twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme);
1477
    }
1478
1479
    private function handleXmlHttpRequestErrorResponse(Request $request, FormInterface $form): JsonResponse
1480
    {
1481
        if (empty(array_intersect(['application/json', '*/*'], $request->getAcceptableContentTypes()))) {
1482
            return $this->renderJson([], Response::HTTP_NOT_ACCEPTABLE);
1483
        }
1484
1485
        $errors = [];
1486
        foreach ($form->getErrors(true) as $error) {
1487
            $errors[] = $error->getMessage();
1488
        }
1489
1490
        return $this->renderJson([
1491
            'result' => 'error',
1492
            'errors' => $errors,
1493
        ], Response::HTTP_BAD_REQUEST);
1494
    }
1495
1496
    /**
1497
     * @param object $object
1498
     */
1499
    private function handleXmlHttpRequestSuccessResponse(Request $request, $object): JsonResponse
1500
    {
1501
        if (empty(array_intersect(['application/json', '*/*'], $request->getAcceptableContentTypes()))) {
1502
            return $this->renderJson([], Response::HTTP_NOT_ACCEPTABLE);
1503
        }
1504
1505
        return $this->renderJson([
1506
            'result' => 'ok',
1507
            'objectId' => $this->admin->getNormalizedIdentifier($object),
1508
            'objectName' => $this->escapeHtml($this->admin->toString($object)),
1509
        ], Response::HTTP_OK);
1510
    }
1511
}
1512