Completed
Push — master ( 3fd2dd...aba624 )
by Grégoire
04:19
created

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