Completed
Pull Request — master (#6204)
by
unknown
05:02 queued 02:06
created

CRUDController::preShow()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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