Completed
Pull Request — master (#6093)
by Mathieu
30:06
created

CRUDController::configure()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

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

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1015
     */
1016
    protected function addRenderExtraParams(array $parameters = []): array
1017
    {
1018
        if (!$this->isXmlHttpRequest()) {
1019
            $parameters['breadcrumbs_builder'] = $this->breadcrumbsBuilder;
1020
        }
1021
1022
        $parameters['admin'] = $parameters['admin'] ?? $this->admin;
1023
        $parameters['base_template'] = $parameters['base_template'] ?? $this->getBaseTemplate();
1024
        $parameters['admin_pool'] = $this->pool;
1025
1026
        return $parameters;
1027
    }
1028
1029
    /**
1030
     * Render JSON.
1031
     *
1032
     * @param mixed $data
1033
     * @param int   $status
1034
     * @param array $headers
1035
     *
1036
     * @return JsonResponse with json encoded data
1037
     */
1038
    protected function renderJson($data, $status = Response::HTTP_OK, $headers = [])
1039
    {
1040
        return new JsonResponse($data, $status, $headers);
1041
    }
1042
1043
    /**
1044
     * Returns true if the request is a XMLHttpRequest.
1045
     *
1046
     * @return bool True if the request is an XMLHttpRequest, false otherwise
1047
     */
1048
    protected function isXmlHttpRequest()
1049
    {
1050
        $request = $this->getRequest();
1051
1052
        return $request->isXmlHttpRequest() || $request->get('_xml_http_request');
1053
    }
1054
1055
    /**
1056
     * Returns the correct RESTful verb, given either by the request itself or
1057
     * via the "_method" parameter.
1058
     *
1059
     * @return string HTTP method, either
1060
     */
1061
    protected function getRestMethod()
1062
    {
1063
        $request = $this->getRequest();
1064
1065
        if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) {
1066
            return $request->getMethod();
1067
        }
1068
1069
        return $request->request->get('_method');
1070
    }
1071
1072
    /**
1073
     * Contextualize the admin class depends on the current request.
1074
     *
1075
     * @throws \RuntimeException
1076
     */
1077
    protected function configure(): void
1078
    {
1079
        $request = $this->getRequest();
1080
1081
        $adminCode = $request->get('_sonata_admin');
1082
1083
        if (!$adminCode) {
1084
            throw new \RuntimeException(sprintf(
1085
                'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`',
1086
                static::class,
1087
                $request->get('_route')
1088
            ));
1089
        }
1090
1091
        try {
1092
            $this->admin = $this->pool->getAdminByAdminCode($adminCode);
1093
        } catch (\InvalidArgumentException $e) {
1094
            throw new \RuntimeException(sprintf(
1095
                'Unable to find the admin class related to the current controller (%s)',
1096
                static::class
1097
            ));
1098
        }
1099
1100
        $rootAdmin = $this->admin;
1101
1102
        while ($rootAdmin->isChild()) {
1103
            $rootAdmin->setCurrentChild(true);
1104
            $rootAdmin = $rootAdmin->getParent();
1105
        }
1106
1107
        $rootAdmin->setRequest($request);
1108
1109
        if ($request->get('uniqid')) {
1110
            $this->admin->setUniqid($request->get('uniqid'));
1111
        }
1112
    }
1113
1114
    /**
1115
     * Proxy for the logger service of the container.
1116
     * If no such service is found, a NullLogger is returned.
1117
     */
1118
    protected function getLogger(): LoggerInterface
1119
    {
1120
        return $this->logger;
1121
    }
1122
1123
    /**
1124
     * Returns the base template name.
1125
     *
1126
     * @return string The template name
1127
     */
1128
    protected function getBaseTemplate()
1129
    {
1130
        if ($this->isXmlHttpRequest()) {
1131
            return $this->templateRegistry->getTemplate('ajax');
1132
        }
1133
1134
        return $this->templateRegistry->getTemplate('layout');
1135
    }
1136
1137
    /**
1138
     * @throws \Exception
1139
     */
1140
    protected function handleModelManagerException(\Exception $e): void
1141
    {
1142
        if ($this->kernelDebug) {
1143
            throw $e;
1144
        }
1145
1146
        $context = ['exception' => $e];
1147
        if ($e->getPrevious()) {
1148
            $context['previous_exception_message'] = $e->getPrevious()->getMessage();
1149
        }
1150
        $this->getLogger()->error($e->getMessage(), $context);
1151
    }
1152
1153
    /**
1154
     * Redirect the user depend on this choice.
1155
     *
1156
     * @param object $object
1157
     *
1158
     * @return RedirectResponse
1159
     */
1160
    protected function redirectTo($object)
1161
    {
1162
        $request = $this->getRequest();
1163
1164
        $url = false;
1165
1166
        if (null !== $request->get('btn_update_and_list')) {
1167
            return $this->redirectToList();
1168
        }
1169
        if (null !== $request->get('btn_create_and_list')) {
1170
            return $this->redirectToList();
1171
        }
1172
1173
        if (null !== $request->get('btn_create_and_create')) {
1174
            $params = [];
1175
            if ($this->admin->hasActiveSubClass()) {
1176
                $params['subclass'] = $request->get('subclass');
1177
            }
1178
            $url = $this->admin->generateUrl('create', $params);
1179
        }
1180
1181
        if ('DELETE' === $this->getRestMethod()) {
1182
            return $this->redirectToList();
1183
        }
1184
1185
        if (!$url) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $url of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1186
            foreach (['edit', 'show'] as $route) {
1187
                if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route, $object)) {
1188
                    $url = $this->admin->generateObjectUrl(
1189
                        $route,
1190
                        $object,
1191
                        $this->getSelectedTab($request)
0 ignored issues
show
Bug introduced by
It seems like $request defined by $this->getRequest() on line 1162 can be null; however, Sonata\AdminBundle\Contr...oller::getSelectedTab() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1192
                    );
1193
1194
                    break;
1195
                }
1196
            }
1197
        }
1198
1199
        if (!$url) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $url of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1200
            return $this->redirectToList();
1201
        }
1202
1203
        return new RedirectResponse($url);
1204
    }
1205
1206
    /**
1207
     * Redirects the user to the list view.
1208
     *
1209
     * @return RedirectResponse
1210
     */
1211
    final protected function redirectToList()
1212
    {
1213
        $parameters = [];
1214
1215
        if ($filter = $this->admin->getFilterParameters()) {
1216
            $parameters['filter'] = $filter;
1217
        }
1218
1219
        return $this->redirect($this->admin->generateUrl('list', $parameters));
1220
    }
1221
1222
    /**
1223
     * Returns true if the preview is requested to be shown.
1224
     *
1225
     * @return bool
1226
     */
1227
    protected function isPreviewRequested()
1228
    {
1229
        $request = $this->getRequest();
1230
1231
        return null !== $request->get('btn_preview');
1232
    }
1233
1234
    /**
1235
     * Returns true if the preview has been approved.
1236
     *
1237
     * @return bool
1238
     */
1239
    protected function isPreviewApproved()
1240
    {
1241
        $request = $this->getRequest();
1242
1243
        return null !== $request->get('btn_preview_approve');
1244
    }
1245
1246
    /**
1247
     * Returns true if the request is in the preview workflow.
1248
     *
1249
     * That means either a preview is requested or the preview has already been shown
1250
     * and it got approved/declined.
1251
     *
1252
     * @return bool
1253
     */
1254
    protected function isInPreviewMode()
1255
    {
1256
        return $this->admin->supportsPreviewMode()
1257
        && ($this->isPreviewRequested()
1258
            || $this->isPreviewApproved()
1259
            || $this->isPreviewDeclined());
1260
    }
1261
1262
    /**
1263
     * Returns true if the preview has been declined.
1264
     *
1265
     * @return bool
1266
     */
1267
    protected function isPreviewDeclined()
1268
    {
1269
        $request = $this->getRequest();
1270
1271
        return null !== $request->get('btn_preview_decline');
1272
    }
1273
1274
    /**
1275
     * Gets ACL users.
1276
     */
1277
    protected function getAclUsers(): \Traversable
1278
    {
1279
        $aclUsers = $this->adminObjectAclUserManager instanceof AdminObjectAclUserManager ?
1280
            $this->adminObjectAclUserManager->findUsers() :
1281
            [];
1282
1283
        return \is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
1284
    }
1285
1286
    /**
1287
     * Gets ACL roles.
1288
     */
1289
    protected function getAclRoles(): \Traversable
1290
    {
1291
        $aclRoles = [];
1292
        $roleHierarchy = $this->pool->getContainer()->getParameter('security.role_hierarchy.roles');
1293
1294
        foreach ($this->pool->getAdminServiceIds() as $id) {
1295
            try {
1296
                $admin = $this->pool->getInstance($id);
1297
            } catch (\Exception $e) {
1298
                continue;
1299
            }
1300
1301
            $baseRole = $admin->getSecurityHandler()->getBaseRole($admin);
1302
            foreach ($admin->getSecurityInformation() as $role => $permissions) {
1303
                $role = sprintf($baseRole, $role);
1304
                $aclRoles[] = $role;
1305
            }
1306
        }
1307
1308
        foreach ($roleHierarchy as $name => $roles) {
1309
            $aclRoles[] = $name;
1310
            $aclRoles = array_merge($aclRoles, $roles);
1311
        }
1312
1313
        $aclRoles = array_unique($aclRoles);
1314
1315
        return \is_array($aclRoles) ? new \ArrayIterator($aclRoles) : $aclRoles;
1316
    }
1317
1318
    /**
1319
     * Validate CSRF token for action without form.
1320
     *
1321
     * @param string $intention
1322
     *
1323
     * @throws HttpException
1324
     */
1325
    protected function validateCsrfToken($intention): void
1326
    {
1327
        $request = $this->getRequest();
1328
        $token = $request->get('_sonata_csrf_token');
1329
1330
        if ($this->csrfTokenManager instanceof CsrfTokenManagerInterface) {
1331
            $valid = $this->csrfTokenManager->isTokenValid(new CsrfToken($intention, $token));
1332
        } else {
1333
            return;
1334
        }
1335
1336
        if (!$valid) {
1337
            throw new HttpException(Response::HTTP_BAD_REQUEST, 'The csrf token is not valid, CSRF attack?');
1338
        }
1339
    }
1340
1341
    /**
1342
     * Escape string for html output.
1343
     *
1344
     * @param string $s
1345
     *
1346
     * @return string
1347
     */
1348
    protected function escapeHtml($s)
1349
    {
1350
        return htmlspecialchars((string) $s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1351
    }
1352
1353
    /**
1354
     * Get CSRF token.
1355
     *
1356
     * @param string $intention
1357
     *
1358
     * @return string|false
1359
     */
1360
    protected function getCsrfToken($intention)
1361
    {
1362
        if ($this->csrfTokenManager) {
1363
            return $this->csrfTokenManager->getToken($intention)->getValue();
1364
        }
1365
1366
        return false;
1367
    }
1368
1369
    /**
1370
     * This method can be overloaded in your custom CRUD controller.
1371
     * It's called from createAction.
1372
     *
1373
     * @param object $object
1374
     *
1375
     * @return Response|null
1376
     */
1377
    protected function preCreate(Request $request, $object)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1378
    {
1379
        return null;
1380
    }
1381
1382
    /**
1383
     * This method can be overloaded in your custom CRUD controller.
1384
     * It's called from editAction.
1385
     *
1386
     * @param object $object
1387
     *
1388
     * @return Response|null
1389
     */
1390
    protected function preEdit(Request $request, $object)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1391
    {
1392
        return null;
1393
    }
1394
1395
    /**
1396
     * This method can be overloaded in your custom CRUD controller.
1397
     * It's called from deleteAction.
1398
     *
1399
     * @param object $object
1400
     *
1401
     * @return Response|null
1402
     */
1403
    protected function preDelete(Request $request, $object)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1404
    {
1405
        return null;
1406
    }
1407
1408
    /**
1409
     * This method can be overloaded in your custom CRUD controller.
1410
     * It's called from showAction.
1411
     *
1412
     * @param object $object
1413
     *
1414
     * @return Response|null
1415
     */
1416
    protected function preShow(Request $request, $object)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1417
    {
1418
        return null;
1419
    }
1420
1421
    /**
1422
     * This method can be overloaded in your custom CRUD controller.
1423
     * It's called from listAction.
1424
     *
1425
     * @return Response|null
1426
     */
1427
    protected function preList(Request $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1428
    {
1429
        return null;
1430
    }
1431
1432
    /**
1433
     * Translate a message id.
1434
     *
1435
     * @param string $id
1436
     * @param string $domain
1437
     * @param string $locale
1438
     *
1439
     * @return string translated string
1440
     */
1441
    final protected function trans($id, array $parameters = [], $domain = null, $locale = null)
1442
    {
1443
        $domain = $domain ?: $this->admin->getTranslationDomain();
1444
1445
        return $this->translator->trans($id, $parameters, $domain, $locale);
1446
    }
1447
1448
    private function getSelectedTab(Request $request): array
1449
    {
1450
        return array_filter(['_tab' => $request->request->get('_tab')]);
1451
    }
1452
1453
    private function checkParentChildAssociation(Request $request, object $object): void
1454
    {
1455
        if (!$this->admin->isChild()) {
1456
            return;
1457
        }
1458
1459
        $parentAdmin = $this->admin->getParent();
1460
        $parentId = $request->get($parentAdmin->getIdParameter());
1461
1462
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
1463
        $propertyPath = new PropertyPath($this->admin->getParentAssociationMapping());
1464
1465
        if ($parentAdmin->getObject($parentId) !== $propertyAccessor->getValue($object, $propertyPath)) {
1466
            throw new \RuntimeException(sprintf(
1467
                'There is no association between "%s" and "%s"',
1468
                $parentAdmin->toString($parentAdmin->getObject($parentId)),
1469
                $this->admin->toString($object)
1470
            ));
1471
        }
1472
    }
1473
1474
    /**
1475
     * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
1476
     */
1477
    private function setFormTheme(FormView $formView, ?array $theme = null): void
1478
    {
1479
        $this->twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme);
1480
    }
1481
1482
    private function handleXmlHttpRequestErrorResponse(Request $request, FormInterface $form): JsonResponse
1483
    {
1484
        if (empty(array_intersect(['application/json', '*/*'], $request->getAcceptableContentTypes()))) {
1485
            return $this->renderJson([], Response::HTTP_NOT_ACCEPTABLE);
1486
        }
1487
1488
        $errors = [];
1489
        foreach ($form->getErrors(true) as $error) {
1490
            $errors[] = $error->getMessage();
1491
        }
1492
1493
        return $this->renderJson([
1494
            'result' => 'error',
1495
            'errors' => $errors,
1496
        ], Response::HTTP_BAD_REQUEST);
1497
    }
1498
1499
    /**
1500
     * @param object $object
1501
     */
1502
    private function handleXmlHttpRequestSuccessResponse(Request $request, $object): JsonResponse
1503
    {
1504
        if (empty(array_intersect(['application/json', '*/*'], $request->getAcceptableContentTypes()))) {
1505
            return $this->renderJson([], Response::HTTP_NOT_ACCEPTABLE);
1506
        }
1507
1508
        return $this->renderJson([
1509
            'result' => 'ok',
1510
            'objectId' => $this->admin->getNormalizedIdentifier($object),
1511
            'objectName' => $this->escapeHtml($this->admin->toString($object)),
1512
        ], Response::HTTP_OK);
1513
    }
1514
}
1515