Completed
Push — 3.x ( e95e95...638cd1 )
by Oskar
05:54
created

src/Controller/CRUDController.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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