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