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

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