Completed
Push — 3.x ( cd4ff0...3fa3f4 )
by Jordi Sala
03:07
created

CRUDController::getAclRoles()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 9.1448
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\AbstractAdmin;
20
use Sonata\AdminBundle\Admin\AdminInterface;
21
use Sonata\AdminBundle\Admin\FieldDescriptionCollection;
22
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
23
use Sonata\AdminBundle\Exception\LockException;
24
use Sonata\AdminBundle\Exception\ModelManagerException;
25
use Sonata\AdminBundle\Templating\TemplateRegistryInterface;
26
use Sonata\AdminBundle\Util\AdminObjectAclData;
27
use Sonata\AdminBundle\Util\AdminObjectAclManipulator;
28
use Symfony\Bundle\FrameworkBundle\Controller\ControllerTrait;
29
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
30
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
31
use Symfony\Component\DependencyInjection\ContainerInterface;
32
use Symfony\Component\Form\FormInterface;
33
use Symfony\Component\Form\FormRenderer;
34
use Symfony\Component\Form\FormView;
35
use Symfony\Component\HttpFoundation\JsonResponse;
36
use Symfony\Component\HttpFoundation\RedirectResponse;
37
use Symfony\Component\HttpFoundation\Request;
38
use Symfony\Component\HttpFoundation\Response;
39
use Symfony\Component\HttpKernel\Exception\HttpException;
40
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
41
use Symfony\Component\PropertyAccess\PropertyAccess;
42
use Symfony\Component\PropertyAccess\PropertyPath;
43
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
44
use Symfony\Component\Security\Csrf\CsrfToken;
45
46
/**
47
 * @author Thomas Rabaix <[email protected]>
48
 */
49
class CRUDController implements ContainerAwareInterface
50
{
51
    // NEXT_MAJOR: Don't use these traits anymore (inherit from Controller instead)
52
    use ControllerTrait, ContainerAwareTrait {
53
        ControllerTrait::render as originalRender;
54
    }
55
56
    /**
57
     * @var ContainerInterface
58
     */
59
    protected $container;
60
61
    /**
62
     * The related Admin class.
63
     *
64
     * @var AdminInterface
65
     */
66
    protected $admin;
67
68
    /**
69
     * The template registry of the related Admin class.
70
     *
71
     * @var TemplateRegistryInterface
72
     */
73
    private $templateRegistry;
74
75
    public function setContainer(?ContainerInterface $container = null)
76
    {
77
        $this->container = $container;
78
79
        $this->configure();
80
    }
81
82
    /**
83
     * NEXT_MAJOR: Remove this method.
84
     *
85
     * @see renderWithExtraParams()
86
     *
87
     * @param string               $view       The view name
88
     * @param array<string, mixed> $parameters An array of parameters to pass to the view
89
     *
90
     * @return Response A Response instance
91
     *
92
     * @deprecated since sonata-project/admin-bundle 3.27, to be removed in 4.0. Use Sonata\AdminBundle\Controller\CRUDController::renderWithExtraParams() instead.
93
     */
94
    public function render($view, array $parameters = [], ?Response $response = null)
95
    {
96
        @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
97
            'Method %1$s::render has been renamed to %1$s::renderWithExtraParams.',
98
            __CLASS__
99
        ), E_USER_DEPRECATED);
100
101
        return $this->renderWithExtraParams($view, $parameters, $response);
102
    }
103
104
    /**
105
     * Renders a view while passing mandatory parameters on to the template.
106
     *
107
     * @param string               $view       The view name
108
     * @param array<string, mixed> $parameters An array of parameters to pass to the view
109
     *
110
     * @return Response A Response instance
111
     */
112
    public function renderWithExtraParams($view, array $parameters = [], ?Response $response = null)
113
    {
114
        //NEXT_MAJOR: Remove method alias and use $this->render() directly.
115
        return $this->originalRender($view, $this->addRenderExtraParams($parameters), $response);
116
    }
117
118
    /**
119
     * List action.
120
     *
121
     * @throws AccessDeniedException If access is not granted
122
     *
123
     * @return Response
124
     */
125
    public function listAction()
126
    {
127
        $request = $this->getRequest();
128
129
        $this->admin->checkAccess('list');
130
131
        $preResponse = $this->preList($request);
132
        if (null !== $preResponse) {
133
            return $preResponse;
134
        }
135
136
        if ($listMode = $request->get('_list_mode')) {
137
            $this->admin->setListMode($listMode);
138
        }
139
140
        $datagrid = $this->admin->getDatagrid();
141
        $formView = $datagrid->getForm()->createView();
142
143
        // set the theme for the current Admin Form
144
        $this->setFormTheme($formView, $this->admin->getFilterTheme());
145
146
        // NEXT_MAJOR: Remove this line and use commented line below it instead
147
        $template = $this->admin->getTemplate('list');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
148
        // $template = $this->templateRegistry->getTemplate('list');
149
150
        return $this->renderWithExtraParams($template, [
151
            'action' => 'list',
152
            'form' => $formView,
153
            'datagrid' => $datagrid,
154
            'csrf_token' => $this->getCsrfToken('sonata.batch'),
155
            'export_formats' => $this->has('sonata.admin.admin_exporter') ?
156
                $this->get('sonata.admin.admin_exporter')->getAvailableFormats($this->admin) :
157
                $this->admin->getExportFormats(),
158
        ], null);
159
    }
160
161
    /**
162
     * Execute a batch delete.
163
     *
164
     * @throws AccessDeniedException If access is not granted
165
     *
166
     * @return RedirectResponse
167
     */
168
    public function batchActionDelete(ProxyQueryInterface $query)
169
    {
170
        $this->admin->checkAccess('batchDelete');
171
172
        $modelManager = $this->admin->getModelManager();
173
174
        try {
175
            $modelManager->batchDelete($this->admin->getClass(), $query);
176
            $this->addFlash(
177
                'sonata_flash_success',
178
                $this->trans('flash_batch_delete_success', [], 'SonataAdminBundle')
179
            );
180
        } catch (ModelManagerException $e) {
181
            $this->handleModelManagerException($e);
182
            $this->addFlash(
183
                'sonata_flash_error',
184
                $this->trans('flash_batch_delete_error', [], 'SonataAdminBundle')
185
            );
186
        }
187
188
        return $this->redirectToList();
189
    }
190
191
    /**
192
     * Delete action.
193
     *
194
     * @param int|string|null $id
195
     *
196
     * @throws NotFoundHttpException If the object does not exist
197
     * @throws AccessDeniedException If access is not granted
198
     *
199
     * @return Response|RedirectResponse
200
     */
201
    public function deleteAction($id) // NEXT_MAJOR: Remove the unused $id parameter
0 ignored issues
show
Unused Code introduced by
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...
202
    {
203
        $request = $this->getRequest();
204
        $id = $request->get($this->admin->getIdParameter());
205
        $object = $this->admin->getObject($id);
206
207
        if (!$object) {
208
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
209
        }
210
211
        $this->checkParentChildAssociation($request, $object);
212
213
        $this->admin->checkAccess('delete', $object);
214
215
        $preResponse = $this->preDelete($request, $object);
216
        if (null !== $preResponse) {
217
            return $preResponse;
218
        }
219
220
        if (Request::METHOD_DELETE === $this->getRestMethod()) {
221
            // check the csrf token
222
            $this->validateCsrfToken('sonata.delete');
223
224
            $objectName = $this->admin->toString($object);
225
226
            try {
227
                $this->admin->delete($object);
228
229
                if ($this->isXmlHttpRequest()) {
230
                    return $this->renderJson(['result' => 'ok'], Response::HTTP_OK, []);
231
                }
232
233
                $this->addFlash(
234
                    'sonata_flash_success',
235
                    $this->trans(
236
                        'flash_delete_success',
237
                        ['%name%' => $this->escapeHtml($objectName)],
238
                        'SonataAdminBundle'
239
                    )
240
                );
241
            } catch (ModelManagerException $e) {
242
                $this->handleModelManagerException($e);
243
244
                if ($this->isXmlHttpRequest()) {
245
                    return $this->renderJson(['result' => 'error'], Response::HTTP_OK, []);
246
                }
247
248
                $this->addFlash(
249
                    'sonata_flash_error',
250
                    $this->trans(
251
                        'flash_delete_error',
252
                        ['%name%' => $this->escapeHtml($objectName)],
253
                        'SonataAdminBundle'
254
                    )
255
                );
256
            }
257
258
            return $this->redirectTo($object);
259
        }
260
261
        // NEXT_MAJOR: Remove this line and use commented line below it instead
262
        $template = $this->admin->getTemplate('delete');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
263
        // $template = $this->templateRegistry->getTemplate('delete');
264
265
        return $this->renderWithExtraParams($template, [
266
            'object' => $object,
267
            'action' => 'delete',
268
            'csrf_token' => $this->getCsrfToken('sonata.delete'),
269
        ], null);
270
    }
271
272
    /**
273
     * Edit action.
274
     *
275
     * @param int|string|null $deprecatedId
276
     *
277
     * @throws NotFoundHttpException If the object does not exist
278
     * @throws AccessDeniedException If access is not granted
279
     *
280
     * @return Response|RedirectResponse
281
     */
282
    public function editAction($deprecatedId = null) // NEXT_MAJOR: Remove the unused $id parameter
0 ignored issues
show
Unused Code introduced by
The parameter $deprecatedId 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...
283
    {
284
        if (isset(\func_get_args()[0])) {
285
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
286
                'Support for the "id" route param as argument 1 at `%s()` is deprecated since'
287
                .' sonata-project/admin-bundle 3.62 and will be removed in 4.0,'
288
                .' use `AdminInterface::getIdParameter()` instead.',
289
                __METHOD__
290
            ), E_USER_DEPRECATED);
291
        }
292
293
        // the key used to lookup the template
294
        $templateKey = 'edit';
295
296
        $request = $this->getRequest();
297
        $id = $request->get($this->admin->getIdParameter());
298
        $existingObject = $this->admin->getObject($id);
299
300
        if (!$existingObject) {
301
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
302
        }
303
304
        $this->checkParentChildAssociation($request, $existingObject);
305
306
        $this->admin->checkAccess('edit', $existingObject);
307
308
        $preResponse = $this->preEdit($request, $existingObject);
309
        if (null !== $preResponse) {
310
            return $preResponse;
311
        }
312
313
        $this->admin->setSubject($existingObject);
314
        $objectId = $this->admin->getNormalizedIdentifier($existingObject);
315
316
        $form = $this->admin->getForm();
317
318
        $form->setData($existingObject);
319
        $form->handleRequest($request);
320
321
        if ($form->isSubmitted()) {
322
            $isFormValid = $form->isValid();
323
324
            // persist if the form was valid and if in preview mode the preview was approved
325
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
326
                $submittedObject = $form->getData();
327
                $this->admin->setSubject($submittedObject);
328
329
                try {
330
                    $existingObject = $this->admin->update($submittedObject);
331
332
                    if ($this->isXmlHttpRequest()) {
333
                        return $this->handleXmlHttpRequestSuccessResponse($request, $existingObject);
334
                    }
335
336
                    $this->addFlash(
337
                        'sonata_flash_success',
338
                        $this->trans(
339
                            'flash_edit_success',
340
                            ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
341
                            'SonataAdminBundle'
342
                        )
343
                    );
344
345
                    // redirect to edit mode
346
                    return $this->redirectTo($existingObject);
347
                } catch (ModelManagerException $e) {
348
                    $this->handleModelManagerException($e);
349
350
                    $isFormValid = false;
351
                } catch (LockException $e) {
352
                    $this->addFlash('sonata_flash_error', $this->trans('flash_lock_error', [
353
                        '%name%' => $this->escapeHtml($this->admin->toString($existingObject)),
354
                        '%link_start%' => sprintf('<a href="%s">', $this->admin->generateObjectUrl('edit', $existingObject)),
355
                        '%link_end%' => '</a>',
356
                    ], 'SonataAdminBundle'));
357
                }
358
            }
359
360
            // show an error message if the form failed validation
361
            if (!$isFormValid) {
362
                if ($this->isXmlHttpRequest() && null !== ($response = $this->handleXmlHttpRequestErrorResponse($request, $form))) {
363
                    return $response;
364
                }
365
366
                $this->addFlash(
367
                    'sonata_flash_error',
368
                    $this->trans(
369
                        'flash_edit_error',
370
                        ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
371
                        'SonataAdminBundle'
372
                    )
373
                );
374
            } elseif ($this->isPreviewRequested()) {
375
                // enable the preview template if the form was valid and preview was requested
376
                $templateKey = 'preview';
377
                $this->admin->getShow();
378
            }
379
        }
380
381
        $formView = $form->createView();
382
        // set the theme for the current Admin Form
383
        $this->setFormTheme($formView, $this->admin->getFormTheme());
384
385
        // NEXT_MAJOR: Remove this line and use commented line below it instead
386
        $template = $this->admin->getTemplate($templateKey);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
387
        // $template = $this->templateRegistry->getTemplate($templateKey);
388
389
        return $this->renderWithExtraParams($template, [
390
            'action' => 'edit',
391
            'form' => $formView,
392
            'object' => $existingObject,
393
            'objectId' => $objectId,
394
        ], null);
395
    }
396
397
    /**
398
     * Batch action.
399
     *
400
     * @throws NotFoundHttpException If the HTTP method is not POST
401
     * @throws \RuntimeException     If the batch action is not defined
402
     *
403
     * @return Response|RedirectResponse
404
     */
405
    public function batchAction()
406
    {
407
        $request = $this->getRequest();
408
        $restMethod = $this->getRestMethod();
409
410
        if (Request::METHOD_POST !== $restMethod) {
411
            throw $this->createNotFoundException(sprintf(
412
                'Invalid request method given "%s", %s expected',
413
                $restMethod,
414
                Request::METHOD_POST
415
            ));
416
        }
417
418
        // check the csrf token
419
        $this->validateCsrfToken('sonata.batch');
420
421
        $confirmation = $request->get('confirmation', false);
422
423
        if ($data = json_decode((string) $request->get('data'), true)) {
424
            $action = $data['action'];
425
            $idx = $data['idx'];
426
            $allElements = (bool) $data['all_elements'];
427
            $request->request->replace(array_merge($request->request->all(), $data));
428
        } else {
429
            $action = $request->request->getAlnum('action');
430
            $idx = $request->request->get('idx', []);
431
            $allElements = $request->request->getBoolean('all_elements');
432
            $data = $request->request->all();
433
434
            unset($data['_sonata_csrf_token']);
435
        }
436
437
        // NEXT_MAJOR: Remove reflection check.
438
        $reflector = new \ReflectionMethod($this->admin, 'getBatchActions');
439
        if ($reflector->getDeclaringClass()->getName() === \get_class($this->admin)) {
0 ignored issues
show
introduced by
Consider using $reflector->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
440
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
441
                'Override %1$s::getBatchActions method is deprecated since version 3.2.'
442
                .' Use %1$s::configureBatchActions instead. The method will be final in 4.0.',
443
                AbstractAdmin::class
444
            ), E_USER_DEPRECATED);
445
        }
446
        $batchActions = $this->admin->getBatchActions();
447
        if (!\array_key_exists($action, $batchActions)) {
448
            throw new \RuntimeException(sprintf('The `%s` batch action is not defined', $action));
449
        }
450
451
        $camelizedAction = InflectorFactory::create()->build()->classify($action);
452
        $isRelevantAction = sprintf('batchAction%sIsRelevant', $camelizedAction);
453
454
        if (method_exists($this, $isRelevantAction)) {
455
            $nonRelevantMessage = $this->{$isRelevantAction}($idx, $allElements, $request);
456
        } else {
457
            $nonRelevantMessage = 0 !== \count($idx) || $allElements; // at least one item is selected
458
        }
459
460
        if (!$nonRelevantMessage) { // default non relevant message (if false of null)
461
            $nonRelevantMessage = 'flash_batch_empty';
462
        }
463
464
        $datagrid = $this->admin->getDatagrid();
465
        $datagrid->buildPager();
466
467
        if (true !== $nonRelevantMessage) {
468
            $this->addFlash(
469
                'sonata_flash_info',
470
                $this->trans($nonRelevantMessage, [], 'SonataAdminBundle')
471
            );
472
473
            return $this->redirectToList();
474
        }
475
476
        $askConfirmation = $batchActions[$action]['ask_confirmation'] ??
477
            true;
478
479
        if ($askConfirmation && 'ok' !== $confirmation) {
480
            $actionLabel = $batchActions[$action]['label'];
481
            $batchTranslationDomain = $batchActions[$action]['translation_domain'] ??
482
                $this->admin->getTranslationDomain();
483
484
            $formView = $datagrid->getForm()->createView();
485
            $this->setFormTheme($formView, $this->admin->getFilterTheme());
486
487
            // NEXT_MAJOR: Remove these lines and use commented lines below them instead
488
            $template = !empty($batchActions[$action]['template']) ?
489
                $batchActions[$action]['template'] :
490
                $this->admin->getTemplate('batch_confirmation');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
491
            // $template = !empty($batchActions[$action]['template']) ?
492
            //     $batchActions[$action]['template'] :
493
            //     $this->templateRegistry->getTemplate('batch_confirmation');
494
495
            return $this->renderWithExtraParams($template, [
496
                'action' => 'list',
497
                'action_label' => $actionLabel,
498
                'batch_translation_domain' => $batchTranslationDomain,
499
                'datagrid' => $datagrid,
500
                'form' => $formView,
501
                'data' => $data,
502
                'csrf_token' => $this->getCsrfToken('sonata.batch'),
503
            ], null);
504
        }
505
506
        // execute the action, batchActionXxxxx
507
        $finalAction = sprintf('batchAction%s', $camelizedAction);
508
        if (!method_exists($this, $finalAction)) {
509
            throw new \RuntimeException(sprintf('A `%s::%s` method must be callable', static::class, $finalAction));
510
        }
511
512
        $query = $datagrid->getQuery();
513
514
        $query->setFirstResult(null);
515
        $query->setMaxResults(null);
516
517
        $this->admin->preBatchAction($action, $query, $idx, $allElements);
518
519
        if (\count($idx) > 0) {
520
            $this->admin->getModelManager()->addIdentifiersToQuery($this->admin->getClass(), $query, $idx);
521
        } elseif (!$allElements) {
522
            $this->addFlash(
523
                'sonata_flash_info',
524
                $this->trans('flash_batch_no_elements_processed', [], 'SonataAdminBundle')
525
            );
526
527
            return $this->redirectToList();
528
        }
529
530
        return $this->{$finalAction}($query, $request);
531
    }
532
533
    /**
534
     * Create action.
535
     *
536
     * @throws AccessDeniedException If access is not granted
537
     *
538
     * @return Response
539
     */
540
    public function createAction()
541
    {
542
        $request = $this->getRequest();
543
        // the key used to lookup the template
544
        $templateKey = 'edit';
545
546
        $this->admin->checkAccess('create');
547
548
        $class = new \ReflectionClass($this->admin->hasActiveSubClass() ? $this->admin->getActiveSubClass() : $this->admin->getClass());
549
550
        if ($class->isAbstract()) {
551
            return $this->renderWithExtraParams(
552
                '@SonataAdmin/CRUD/select_subclass.html.twig',
553
                [
554
                    'base_template' => $this->getBaseTemplate(),
555
                    'admin' => $this->admin,
556
                    'action' => 'create',
557
                ],
558
                null
559
            );
560
        }
561
562
        $newObject = $this->admin->getNewInstance();
563
564
        $preResponse = $this->preCreate($request, $newObject);
565
        if (null !== $preResponse) {
566
            return $preResponse;
567
        }
568
569
        $this->admin->setSubject($newObject);
570
571
        $form = $this->admin->getForm();
572
573
        $form->setData($newObject);
574
        $form->handleRequest($request);
575
576
        if ($form->isSubmitted()) {
577
            $isFormValid = $form->isValid();
578
579
            // persist if the form was valid and if in preview mode the preview was approved
580
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
581
                $submittedObject = $form->getData();
582
                $this->admin->setSubject($submittedObject);
583
                $this->admin->checkAccess('create', $submittedObject);
584
585
                try {
586
                    $newObject = $this->admin->create($submittedObject);
587
588
                    if ($this->isXmlHttpRequest()) {
589
                        return $this->handleXmlHttpRequestSuccessResponse($request, $newObject);
590
                    }
591
592
                    $this->addFlash(
593
                        'sonata_flash_success',
594
                        $this->trans(
595
                            'flash_create_success',
596
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
597
                            'SonataAdminBundle'
598
                        )
599
                    );
600
601
                    // redirect to edit mode
602
                    return $this->redirectTo($newObject);
603
                } catch (ModelManagerException $e) {
604
                    $this->handleModelManagerException($e);
605
606
                    $isFormValid = false;
607
                }
608
            }
609
610
            // show an error message if the form failed validation
611
            if (!$isFormValid) {
612
                if ($this->isXmlHttpRequest() && null !== ($response = $this->handleXmlHttpRequestErrorResponse($request, $form))) {
613
                    return $response;
614
                }
615
616
                $this->addFlash(
617
                    'sonata_flash_error',
618
                    $this->trans(
619
                        'flash_create_error',
620
                        ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
621
                        'SonataAdminBundle'
622
                    )
623
                );
624
            } elseif ($this->isPreviewRequested()) {
625
                // pick the preview template if the form was valid and preview was requested
626
                $templateKey = 'preview';
627
                $this->admin->getShow();
628
            }
629
        }
630
631
        $formView = $form->createView();
632
        // set the theme for the current Admin Form
633
        $this->setFormTheme($formView, $this->admin->getFormTheme());
634
635
        // NEXT_MAJOR: Remove this line and use commented line below it instead
636
        $template = $this->admin->getTemplate($templateKey);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
637
        // $template = $this->templateRegistry->getTemplate($templateKey);
638
639
        return $this->renderWithExtraParams($template, [
640
            'action' => 'create',
641
            'form' => $formView,
642
            'object' => $newObject,
643
            'objectId' => null,
644
        ], null);
645
    }
646
647
    /**
648
     * Show action.
649
     *
650
     * @param int|string|null $deprecatedId
651
     *
652
     * @throws NotFoundHttpException If the object does not exist
653
     * @throws AccessDeniedException If access is not granted
654
     *
655
     * @return Response
656
     */
657
    public function showAction($deprecatedId = null) // NEXT_MAJOR: Remove the unused $id parameter
0 ignored issues
show
Unused Code introduced by
The parameter $deprecatedId 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...
658
    {
659
        if (isset(\func_get_args()[0])) {
660
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
661
                'Support for the "id" route param as argument 1 at `%s()` is deprecated since'
662
                .' sonata-project/admin-bundle 3.62 and will be removed in 4.0,'
663
                .' use `AdminInterface::getIdParameter()` instead.',
664
                __METHOD__
665
            ), E_USER_DEPRECATED);
666
        }
667
668
        $request = $this->getRequest();
669
        $id = $request->get($this->admin->getIdParameter());
670
        $object = $this->admin->getObject($id);
671
672
        if (!$object) {
673
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
674
        }
675
676
        $this->checkParentChildAssociation($request, $object);
677
678
        $this->admin->checkAccess('show', $object);
679
680
        $preResponse = $this->preShow($request, $object);
681
        if (null !== $preResponse) {
682
            return $preResponse;
683
        }
684
685
        $this->admin->setSubject($object);
686
687
        $fields = $this->admin->getShow();
688
        \assert($fields instanceof FieldDescriptionCollection);
689
690
        // NEXT_MAJOR: Remove this line and use commented line below it instead
691
        $template = $this->admin->getTemplate('show');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
692
        //$template = $this->templateRegistry->getTemplate('show');
693
694
        return $this->renderWithExtraParams($template, [
695
            'action' => 'show',
696
            'object' => $object,
697
            'elements' => $fields,
698
        ], null);
699
    }
700
701
    /**
702
     * Show history revisions for object.
703
     *
704
     * @param int|string|null $deprecatedId
705
     *
706
     * @throws AccessDeniedException If access is not granted
707
     * @throws NotFoundHttpException If the object does not exist or the audit reader is not available
708
     *
709
     * @return Response
710
     */
711
    public function historyAction($deprecatedId = null) // NEXT_MAJOR: Remove the unused $id parameter
0 ignored issues
show
Unused Code introduced by
The parameter $deprecatedId 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...
712
    {
713
        if (isset(\func_get_args()[0])) {
714
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
715
                'Support for the "id" route param as argument 1 at `%s()` is deprecated since'
716
                .' sonata-project/admin-bundle 3.62 and will be removed in 4.0,'
717
                .' use `AdminInterface::getIdParameter()` instead.',
718
                __METHOD__
719
            ), E_USER_DEPRECATED);
720
        }
721
722
        $request = $this->getRequest();
723
        $id = $request->get($this->admin->getIdParameter());
724
        $object = $this->admin->getObject($id);
725
726
        if (!$object) {
727
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
728
        }
729
730
        $this->admin->checkAccess('history', $object);
731
732
        $manager = $this->get('sonata.admin.audit.manager');
733
734
        if (!$manager->hasReader($this->admin->getClass())) {
735
            throw $this->createNotFoundException(sprintf(
736
                'unable to find the audit reader for class : %s',
737
                $this->admin->getClass()
738
            ));
739
        }
740
741
        $reader = $manager->getReader($this->admin->getClass());
742
743
        $revisions = $reader->findRevisions($this->admin->getClass(), $id);
744
745
        // NEXT_MAJOR: Remove this line and use commented line below it instead
746
        $template = $this->admin->getTemplate('history');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
747
        // $template = $this->templateRegistry->getTemplate('history');
748
749
        return $this->renderWithExtraParams($template, [
750
            'action' => 'history',
751
            'object' => $object,
752
            'revisions' => $revisions,
753
            'currentRevision' => $revisions ? current($revisions) : false,
754
        ], null);
755
    }
756
757
    /**
758
     * View history revision of object.
759
     *
760
     * @param int|string|null $id
761
     * @param string|null     $revision
762
     *
763
     * @throws AccessDeniedException If access is not granted
764
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
765
     *
766
     * @return Response
767
     */
768
    public function historyViewRevisionAction($id = null, $revision = null) // NEXT_MAJOR: Remove the unused $id parameter
0 ignored issues
show
Unused Code introduced by
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...
769
    {
770
        $request = $this->getRequest();
771
        $id = $request->get($this->admin->getIdParameter());
772
        $object = $this->admin->getObject($id);
773
774
        if (!$object) {
775
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
776
        }
777
778
        $this->admin->checkAccess('historyViewRevision', $object);
779
780
        $manager = $this->get('sonata.admin.audit.manager');
781
782
        if (!$manager->hasReader($this->admin->getClass())) {
783
            throw $this->createNotFoundException(sprintf(
784
                'unable to find the audit reader for class : %s',
785
                $this->admin->getClass()
786
            ));
787
        }
788
789
        $reader = $manager->getReader($this->admin->getClass());
790
791
        // retrieve the revisioned object
792
        $object = $reader->find($this->admin->getClass(), $id, $revision);
793
794
        if (!$object) {
795
            throw $this->createNotFoundException(sprintf(
796
                'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
797
                $id,
798
                $revision,
799
                $this->admin->getClass()
800
            ));
801
        }
802
803
        $this->admin->setSubject($object);
804
805
        // NEXT_MAJOR: Remove this line and use commented line below it instead
806
        $template = $this->admin->getTemplate('show');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
807
        // $template = $this->templateRegistry->getTemplate('show');
808
809
        return $this->renderWithExtraParams($template, [
810
            'action' => 'show',
811
            'object' => $object,
812
            'elements' => $this->admin->getShow(),
813
        ], null);
814
    }
815
816
    /**
817
     * Compare history revisions of object.
818
     *
819
     * @param int|string|null $id
820
     * @param int|string|null $base_revision
821
     * @param int|string|null $compare_revision
822
     *
823
     * @throws AccessDeniedException If access is not granted
824
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
825
     *
826
     * @return Response
827
     */
828
    public function historyCompareRevisionsAction($id = null, $base_revision = null, $compare_revision = null) // NEXT_MAJOR: Remove the unused $id parameter
0 ignored issues
show
Unused Code introduced by
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...
829
    {
830
        $this->admin->checkAccess('historyCompareRevisions');
831
832
        $request = $this->getRequest();
833
        $id = $request->get($this->admin->getIdParameter());
834
        $object = $this->admin->getObject($id);
835
836
        if (!$object) {
837
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
838
        }
839
840
        $manager = $this->get('sonata.admin.audit.manager');
841
842
        if (!$manager->hasReader($this->admin->getClass())) {
843
            throw $this->createNotFoundException(sprintf(
844
                'unable to find the audit reader for class : %s',
845
                $this->admin->getClass()
846
            ));
847
        }
848
849
        $reader = $manager->getReader($this->admin->getClass());
850
851
        // retrieve the base revision
852
        $base_object = $reader->find($this->admin->getClass(), $id, $base_revision);
853
        if (!$base_object) {
854
            throw $this->createNotFoundException(sprintf(
855
                'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
856
                $id,
857
                $base_revision,
858
                $this->admin->getClass()
859
            ));
860
        }
861
862
        // retrieve the compare revision
863
        $compare_object = $reader->find($this->admin->getClass(), $id, $compare_revision);
864
        if (!$compare_object) {
865
            throw $this->createNotFoundException(sprintf(
866
                'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
867
                $id,
868
                $compare_revision,
869
                $this->admin->getClass()
870
            ));
871
        }
872
873
        $this->admin->setSubject($base_object);
874
875
        // NEXT_MAJOR: Remove this line and use commented line below it instead
876
        $template = $this->admin->getTemplate('show_compare');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
877
        // $template = $this->templateRegistry->getTemplate('show_compare');
878
879
        return $this->renderWithExtraParams($template, [
880
            'action' => 'show',
881
            'object' => $base_object,
882
            'object_compare' => $compare_object,
883
            'elements' => $this->admin->getShow(),
884
        ], null);
885
    }
886
887
    /**
888
     * Export data to specified format.
889
     *
890
     * @throws AccessDeniedException If access is not granted
891
     * @throws \RuntimeException     If the export format is invalid
892
     *
893
     * @return Response
894
     */
895
    public function exportAction(Request $request)
896
    {
897
        $this->admin->checkAccess('export');
898
899
        $format = $request->get('format');
900
901
        // NEXT_MAJOR: remove the check
902
        if (!$this->has('sonata.admin.admin_exporter')) {
903
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
904
                'Not registering the exporter bundle is deprecated since version 3.14. You must register it to be able to use the export action in 4.0.',
905
                E_USER_DEPRECATED
906
            );
907
            $allowedExportFormats = (array) $this->admin->getExportFormats();
908
909
            $class = (string) $this->admin->getClass();
910
            $filename = sprintf(
911
                'export_%s_%s.%s',
912
                strtolower((string) substr($class, strripos($class, '\\') + 1)),
913
                date('Y_m_d_H_i_s', strtotime('now')),
914
                $format
915
            );
916
            $exporter = $this->get('sonata.admin.exporter');
917
        } else {
918
            $adminExporter = $this->get('sonata.admin.admin_exporter');
919
            $allowedExportFormats = $adminExporter->getAvailableFormats($this->admin);
920
            $filename = $adminExporter->getExportFilename($this->admin, $format);
921
            $exporter = $this->get('sonata.exporter.exporter');
922
        }
923
924
        if (!\in_array($format, $allowedExportFormats, true)) {
925
            throw new \RuntimeException(sprintf(
926
                'Export in format `%s` is not allowed for class: `%s`. Allowed formats are: `%s`',
927
                $format,
928
                $this->admin->getClass(),
929
                implode(', ', $allowedExportFormats)
930
            ));
931
        }
932
933
        return $exporter->getResponse(
934
            $format,
935
            $filename,
936
            $this->admin->getDataSourceIterator()
937
        );
938
    }
939
940
    /**
941
     * Returns the Response object associated to the acl action.
942
     *
943
     * @param int|string|null $deprecatedId
944
     *
945
     * @throws AccessDeniedException If access is not granted
946
     * @throws NotFoundHttpException If the object does not exist or the ACL is not enabled
947
     *
948
     * @return Response|RedirectResponse
949
     */
950
    public function aclAction($deprecatedId = null) // NEXT_MAJOR: Remove the unused $id parameter
0 ignored issues
show
Unused Code introduced by
The parameter $deprecatedId 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...
951
    {
952
        if (isset(\func_get_args()[0])) {
953
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
954
                'Support for the "id" route param as argument 1 at `%s()` is deprecated since'
955
                .' sonata-project/admin-bundle 3.62 and will be removed in 4.0,'
956
                .' use `AdminInterface::getIdParameter()` instead.',
957
                __METHOD__
958
            ), E_USER_DEPRECATED);
959
        }
960
961
        if (!$this->admin->isAclEnabled()) {
962
            throw $this->createNotFoundException('ACL are not enabled for this admin');
963
        }
964
965
        $request = $this->getRequest();
966
        $id = $request->get($this->admin->getIdParameter());
967
        $object = $this->admin->getObject($id);
968
969
        if (!$object) {
970
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
971
        }
972
973
        $this->admin->checkAccess('acl', $object);
974
975
        $this->admin->setSubject($object);
976
        $aclUsers = $this->getAclUsers();
977
        $aclRoles = $this->getAclRoles();
978
979
        $adminObjectAclManipulator = $this->get('sonata.admin.object.manipulator.acl.admin');
980
        $adminObjectAclData = new AdminObjectAclData(
981
            $this->admin,
982
            $object,
983
            $aclUsers,
984
            $adminObjectAclManipulator->getMaskBuilderClass(),
985
            $aclRoles
986
        );
987
988
        $aclUsersForm = $adminObjectAclManipulator->createAclUsersForm($adminObjectAclData);
989
        $aclRolesForm = $adminObjectAclManipulator->createAclRolesForm($adminObjectAclData);
990
991
        if (Request::METHOD_POST === $request->getMethod()) {
992
            if ($request->request->has(AdminObjectAclManipulator::ACL_USERS_FORM_NAME)) {
993
                $form = $aclUsersForm;
994
                $updateMethod = 'updateAclUsers';
995
            } elseif ($request->request->has(AdminObjectAclManipulator::ACL_ROLES_FORM_NAME)) {
996
                $form = $aclRolesForm;
997
                $updateMethod = 'updateAclRoles';
998
            }
999
1000
            if (isset($form, $updateMethod)) {
1001
                $form->handleRequest($request);
1002
1003
                if ($form->isValid()) {
1004
                    $adminObjectAclManipulator->$updateMethod($adminObjectAclData);
1005
                    $this->addFlash(
1006
                        'sonata_flash_success',
1007
                        $this->trans('flash_acl_edit_success', [], 'SonataAdminBundle')
1008
                    );
1009
1010
                    return new RedirectResponse($this->admin->generateObjectUrl('acl', $object));
1011
                }
1012
            }
1013
        }
1014
1015
        // NEXT_MAJOR: Remove this line and use commented line below it instead
1016
        $template = $this->admin->getTemplate('acl');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1017
        // $template = $this->templateRegistry->getTemplate('acl');
1018
1019
        return $this->renderWithExtraParams($template, [
1020
            'action' => 'acl',
1021
            'permissions' => $adminObjectAclData->getUserPermissions(),
1022
            'object' => $object,
1023
            'users' => $aclUsers,
1024
            'roles' => $aclRoles,
1025
            'aclUsersForm' => $aclUsersForm->createView(),
1026
            'aclRolesForm' => $aclRolesForm->createView(),
1027
        ], null);
1028
    }
1029
1030
    /**
1031
     * @return Request
1032
     */
1033
    public function getRequest()
1034
    {
1035
        return $this->container->get('request_stack')->getCurrentRequest();
1036
    }
1037
1038
    /**
1039
     * @param array<string, mixed> $parameters
1040
     *
1041
     * @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...
1042
     */
1043
    protected function addRenderExtraParams(array $parameters = []): array
1044
    {
1045
        if (!$this->isXmlHttpRequest()) {
1046
            $parameters['breadcrumbs_builder'] = $this->get('sonata.admin.breadcrumbs_builder');
1047
        }
1048
1049
        $parameters['admin'] = $parameters['admin'] ?? $this->admin;
1050
        $parameters['base_template'] = $parameters['base_template'] ?? $this->getBaseTemplate();
1051
        $parameters['admin_pool'] = $this->get('sonata.admin.pool');
1052
1053
        return $parameters;
1054
    }
1055
1056
    /**
1057
     * Gets a container configuration parameter by its name.
1058
     *
1059
     * @param string $name The parameter name
1060
     *
1061
     * @return mixed
1062
     */
1063
    protected function getParameter($name)
1064
    {
1065
        return $this->container->getParameter($name);
1066
    }
1067
1068
    /**
1069
     * Render JSON.
1070
     *
1071
     * @param mixed $data
1072
     * @param int   $status
1073
     * @param array $headers
1074
     *
1075
     * @return JsonResponse with json encoded data
1076
     */
1077
    protected function renderJson($data, $status = Response::HTTP_OK, $headers = [])
1078
    {
1079
        return new JsonResponse($data, $status, $headers);
1080
    }
1081
1082
    /**
1083
     * Returns true if the request is a XMLHttpRequest.
1084
     *
1085
     * @return bool True if the request is an XMLHttpRequest, false otherwise
1086
     */
1087
    protected function isXmlHttpRequest()
1088
    {
1089
        $request = $this->getRequest();
1090
1091
        return $request->isXmlHttpRequest() || $request->get('_xml_http_request');
1092
    }
1093
1094
    /**
1095
     * Returns the correct RESTful verb, given either by the request itself or
1096
     * via the "_method" parameter.
1097
     *
1098
     * @return string HTTP method, either
1099
     */
1100
    protected function getRestMethod()
1101
    {
1102
        $request = $this->getRequest();
1103
1104
        if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) {
1105
            return $request->getMethod();
1106
        }
1107
1108
        return $request->request->get('_method');
1109
    }
1110
1111
    /**
1112
     * Contextualize the admin class depends on the current request.
1113
     *
1114
     * @throws \RuntimeException
1115
     */
1116
    protected function configure()
1117
    {
1118
        $request = $this->getRequest();
1119
1120
        $adminCode = $request->get('_sonata_admin');
1121
1122
        if (!$adminCode) {
1123
            throw new \RuntimeException(sprintf(
1124
                'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`',
1125
                static::class,
1126
                $request->get('_route')
1127
            ));
1128
        }
1129
1130
        $this->admin = $this->container->get('sonata.admin.pool')->getAdminByAdminCode($adminCode);
1131
1132
        if (!$this->admin) {
1133
            throw new \RuntimeException(sprintf(
1134
                'Unable to find the admin class related to the current controller (%s)',
1135
                static::class
1136
            ));
1137
        }
1138
1139
        $this->templateRegistry = $this->container->get(sprintf('%s.template_registry', $this->admin->getCode()));
1140
        if (!$this->templateRegistry instanceof TemplateRegistryInterface) {
1141
            throw new \RuntimeException(sprintf(
1142
                'Unable to find the template registry related to the current admin (%s)',
1143
                $this->admin->getCode()
1144
            ));
1145
        }
1146
1147
        $rootAdmin = $this->admin;
1148
1149
        while ($rootAdmin->isChild()) {
1150
            $rootAdmin->setCurrentChild(true);
1151
            $rootAdmin = $rootAdmin->getParent();
1152
        }
1153
1154
        $rootAdmin->setRequest($request);
1155
1156
        if ($request->get('uniqid')) {
1157
            $this->admin->setUniqid($request->get('uniqid'));
1158
        }
1159
    }
1160
1161
    /**
1162
     * Proxy for the logger service of the container.
1163
     * If no such service is found, a NullLogger is returned.
1164
     *
1165
     * @return LoggerInterface
1166
     */
1167
    protected function getLogger()
1168
    {
1169
        if ($this->container->has('logger')) {
1170
            $logger = $this->container->get('logger');
1171
            \assert($logger instanceof LoggerInterface);
1172
1173
            return $logger;
1174
        }
1175
1176
        return new NullLogger();
1177
    }
1178
1179
    /**
1180
     * Returns the base template name.
1181
     *
1182
     * @return string The template name
1183
     */
1184
    protected function getBaseTemplate()
1185
    {
1186
        if ($this->isXmlHttpRequest()) {
1187
            // NEXT_MAJOR: Remove this line and use commented line below it instead
1188
            return $this->admin->getTemplate('ajax');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1189
            // return $this->templateRegistry->getTemplate('ajax');
1190
        }
1191
1192
        // NEXT_MAJOR: Remove this line and use commented line below it instead
1193
        return $this->admin->getTemplate('layout');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1194
        // return $this->templateRegistry->getTemplate('layout');
1195
    }
1196
1197
    /**
1198
     * @throws \Exception
1199
     */
1200
    protected function handleModelManagerException(\Exception $e)
1201
    {
1202
        if ($this->get('kernel')->isDebug()) {
1203
            throw $e;
1204
        }
1205
1206
        $context = ['exception' => $e];
1207
        if ($e->getPrevious()) {
1208
            $context['previous_exception_message'] = $e->getPrevious()->getMessage();
1209
        }
1210
        $this->getLogger()->error($e->getMessage(), $context);
1211
    }
1212
1213
    /**
1214
     * Redirect the user depend on this choice.
1215
     *
1216
     * @param object $object
1217
     *
1218
     * @return RedirectResponse
1219
     */
1220
    protected function redirectTo($object)
1221
    {
1222
        $request = $this->getRequest();
1223
1224
        $url = false;
1225
1226
        if (null !== $request->get('btn_update_and_list')) {
1227
            return $this->redirectToList();
1228
        }
1229
        if (null !== $request->get('btn_create_and_list')) {
1230
            return $this->redirectToList();
1231
        }
1232
1233
        if (null !== $request->get('btn_create_and_create')) {
1234
            $params = [];
1235
            if ($this->admin->hasActiveSubClass()) {
1236
                $params['subclass'] = $request->get('subclass');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $params['subclass'] is correct as $request->get('subclass') (which targets Symfony\Component\HttpFoundation\Request::get()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1237
            }
1238
            $url = $this->admin->generateUrl('create', $params);
1239
        }
1240
1241
        if ('DELETE' === $this->getRestMethod()) {
1242
            return $this->redirectToList();
1243
        }
1244
1245
        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...
1246
            foreach (['edit', 'show'] as $route) {
1247
                if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route, $object)) {
1248
                    $url = $this->admin->generateObjectUrl(
1249
                        $route,
1250
                        $object,
1251
                        $this->getSelectedTab($request)
1252
                    );
1253
1254
                    break;
1255
                }
1256
            }
1257
        }
1258
1259
        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...
1260
            return $this->redirectToList();
1261
        }
1262
1263
        return new RedirectResponse($url);
1264
    }
1265
1266
    /**
1267
     * Redirects the user to the list view.
1268
     *
1269
     * @return RedirectResponse
1270
     */
1271
    final protected function redirectToList()
1272
    {
1273
        $parameters = [];
1274
1275
        if ($filter = $this->admin->getFilterParameters()) {
1276
            $parameters['filter'] = $filter;
1277
        }
1278
1279
        return $this->redirect($this->admin->generateUrl('list', $parameters));
1280
    }
1281
1282
    /**
1283
     * Returns true if the preview is requested to be shown.
1284
     *
1285
     * @return bool
1286
     */
1287
    protected function isPreviewRequested()
1288
    {
1289
        $request = $this->getRequest();
1290
1291
        return null !== $request->get('btn_preview');
1292
    }
1293
1294
    /**
1295
     * Returns true if the preview has been approved.
1296
     *
1297
     * @return bool
1298
     */
1299
    protected function isPreviewApproved()
1300
    {
1301
        $request = $this->getRequest();
1302
1303
        return null !== $request->get('btn_preview_approve');
1304
    }
1305
1306
    /**
1307
     * Returns true if the request is in the preview workflow.
1308
     *
1309
     * That means either a preview is requested or the preview has already been shown
1310
     * and it got approved/declined.
1311
     *
1312
     * @return bool
1313
     */
1314
    protected function isInPreviewMode()
1315
    {
1316
        return $this->admin->supportsPreviewMode()
1317
        && ($this->isPreviewRequested()
1318
            || $this->isPreviewApproved()
1319
            || $this->isPreviewDeclined());
1320
    }
1321
1322
    /**
1323
     * Returns true if the preview has been declined.
1324
     *
1325
     * @return bool
1326
     */
1327
    protected function isPreviewDeclined()
1328
    {
1329
        $request = $this->getRequest();
1330
1331
        return null !== $request->get('btn_preview_decline');
1332
    }
1333
1334
    /**
1335
     * Gets ACL users.
1336
     *
1337
     * @return \Traversable
1338
     */
1339
    protected function getAclUsers()
1340
    {
1341
        $aclUsers = [];
1342
1343
        $userManagerServiceName = $this->container->getParameter('sonata.admin.security.acl_user_manager');
1344
        if (null !== $userManagerServiceName && $this->has($userManagerServiceName)) {
1345
            $userManager = $this->get($userManagerServiceName);
1346
1347
            if (method_exists($userManager, 'findUsers')) {
1348
                $aclUsers = $userManager->findUsers();
1349
            }
1350
        }
1351
1352
        return \is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
1353
    }
1354
1355
    /**
1356
     * Gets ACL roles.
1357
     *
1358
     * @return \Traversable
1359
     */
1360
    protected function getAclRoles()
1361
    {
1362
        $aclRoles = [];
1363
        $roleHierarchy = $this->container->getParameter('security.role_hierarchy.roles');
1364
        $pool = $this->container->get('sonata.admin.pool');
1365
1366
        foreach ($pool->getAdminServiceIds() as $id) {
1367
            try {
1368
                $admin = $pool->getInstance($id);
1369
            } catch (\Exception $e) {
1370
                continue;
1371
            }
1372
1373
            $baseRole = $admin->getSecurityHandler()->getBaseRole($admin);
1374
            foreach ($admin->getSecurityInformation() as $role => $permissions) {
1375
                $role = sprintf($baseRole, $role);
1376
                $aclRoles[] = $role;
1377
            }
1378
        }
1379
1380
        foreach ($roleHierarchy as $name => $roles) {
1381
            $aclRoles[] = $name;
1382
            $aclRoles = array_merge($aclRoles, $roles);
1383
        }
1384
1385
        $aclRoles = array_unique($aclRoles);
1386
1387
        return new \ArrayIterator($aclRoles);
1388
    }
1389
1390
    /**
1391
     * Validate CSRF token for action without form.
1392
     *
1393
     * @param string $intention
1394
     *
1395
     * @throws HttpException
1396
     */
1397
    protected function validateCsrfToken($intention)
1398
    {
1399
        $request = $this->getRequest();
1400
        $token = $request->get('_sonata_csrf_token');
1401
1402
        if ($this->container->has('security.csrf.token_manager')) {
1403
            $valid = $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($intention, $token));
1404
        } else {
1405
            return;
1406
        }
1407
1408
        if (!$valid) {
1409
            throw new HttpException(Response::HTTP_BAD_REQUEST, 'The csrf token is not valid, CSRF attack?');
1410
        }
1411
    }
1412
1413
    /**
1414
     * Escape string for html output.
1415
     *
1416
     * @param string $s
1417
     *
1418
     * @return string
1419
     */
1420
    protected function escapeHtml($s)
1421
    {
1422
        return htmlspecialchars((string) $s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1423
    }
1424
1425
    /**
1426
     * Get CSRF token.
1427
     *
1428
     * @param string $intention
1429
     *
1430
     * @return string|false
1431
     */
1432
    protected function getCsrfToken($intention)
1433
    {
1434
        if ($this->container->has('security.csrf.token_manager')) {
1435
            return $this->container->get('security.csrf.token_manager')->getToken($intention)->getValue();
1436
        }
1437
1438
        return false;
1439
    }
1440
1441
    /**
1442
     * This method can be overloaded in your custom CRUD controller.
1443
     * It's called from createAction.
1444
     *
1445
     * @param object $object
1446
     *
1447
     * @return Response|null
1448
     */
1449
    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...
1450
    {
1451
        return null;
1452
    }
1453
1454
    /**
1455
     * This method can be overloaded in your custom CRUD controller.
1456
     * It's called from editAction.
1457
     *
1458
     * @param object $object
1459
     *
1460
     * @return Response|null
1461
     */
1462
    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...
1463
    {
1464
        return null;
1465
    }
1466
1467
    /**
1468
     * This method can be overloaded in your custom CRUD controller.
1469
     * It's called from deleteAction.
1470
     *
1471
     * @param object $object
1472
     *
1473
     * @return Response|null
1474
     */
1475
    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...
1476
    {
1477
        return null;
1478
    }
1479
1480
    /**
1481
     * This method can be overloaded in your custom CRUD controller.
1482
     * It's called from showAction.
1483
     *
1484
     * @param object $object
1485
     *
1486
     * @return Response|null
1487
     */
1488
    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...
1489
    {
1490
        return null;
1491
    }
1492
1493
    /**
1494
     * This method can be overloaded in your custom CRUD controller.
1495
     * It's called from listAction.
1496
     *
1497
     * @return Response|null
1498
     */
1499
    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...
1500
    {
1501
        return null;
1502
    }
1503
1504
    /**
1505
     * Translate a message id.
1506
     *
1507
     * @param string $id
1508
     * @param string $domain
1509
     * @param string $locale
1510
     *
1511
     * @return string translated string
1512
     */
1513
    final protected function trans($id, array $parameters = [], $domain = null, $locale = null)
1514
    {
1515
        $domain = $domain ?: $this->admin->getTranslationDomain();
1516
1517
        return $this->get('translator')->trans($id, $parameters, $domain, $locale);
1518
    }
1519
1520
    private function getSelectedTab(Request $request): array
1521
    {
1522
        return array_filter(['_tab' => $request->request->get('_tab')]);
1523
    }
1524
1525
    private function checkParentChildAssociation(Request $request, object $object): void
1526
    {
1527
        if (!$this->admin->isChild()) {
1528
            return;
1529
        }
1530
1531
        // NEXT_MAJOR: remove this check
1532
        if (!$this->admin->getParentAssociationMapping()) {
0 ignored issues
show
Bug introduced by
The method getParentAssociationMapping() does not exist on Sonata\AdminBundle\Admin\AdminInterface. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1533
            return;
1534
        }
1535
1536
        $parentAdmin = $this->admin->getParent();
1537
        $parentId = $request->get($parentAdmin->getIdParameter());
1538
1539
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
1540
        $propertyPath = new PropertyPath($this->admin->getParentAssociationMapping());
0 ignored issues
show
Bug introduced by
The method getParentAssociationMapping() does not exist on Sonata\AdminBundle\Admin\AdminInterface. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1541
1542
        if ($parentAdmin->getObject($parentId) !== $propertyAccessor->getValue($object, $propertyPath)) {
1543
            // NEXT_MAJOR: make this exception
1544
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1545
                'Accessing a child that isn\'t connected to a given parent is deprecated since sonata-project/admin-bundle 3.34 and won\'t be allowed in 4.0.',
1546
                E_USER_DEPRECATED
1547
            );
1548
        }
1549
    }
1550
1551
    /**
1552
     * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
1553
     */
1554
    private function setFormTheme(FormView $formView, ?array $theme = null): void
1555
    {
1556
        $twig = $this->get('twig');
1557
1558
        $twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme);
1559
    }
1560
1561
    private function handleXmlHttpRequestErrorResponse(Request $request, FormInterface $form): ?JsonResponse
1562
    {
1563
        if (empty(array_intersect(['application/json', '*/*'], $request->getAcceptableContentTypes()))) {
1564
            @trigger_error('In next major version response will return 406 NOT ACCEPTABLE without `Accept: application/json` or `Accept: */*`', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1565
1566
            return null;
1567
        }
1568
1569
        $errors = [];
1570
        foreach ($form->getErrors(true) as $error) {
1571
            $errors[] = $error->getMessage();
1572
        }
1573
1574
        return $this->renderJson([
1575
            'result' => 'error',
1576
            'errors' => $errors,
1577
        ], Response::HTTP_BAD_REQUEST);
1578
    }
1579
1580
    /**
1581
     * @param object $object
1582
     */
1583
    private function handleXmlHttpRequestSuccessResponse(Request $request, $object): JsonResponse
1584
    {
1585
        if (empty(array_intersect(['application/json', '*/*'], $request->getAcceptableContentTypes()))) {
1586
            @trigger_error('In next major version response will return 406 NOT ACCEPTABLE without `Accept: application/json` or `Accept: */*`', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1587
        }
1588
1589
        return $this->renderJson([
1590
            'result' => 'ok',
1591
            'objectId' => $this->admin->getNormalizedIdentifier($object),
1592
            'objectName' => $this->escapeHtml($this->admin->toString($object)),
1593
        ], Response::HTTP_OK);
1594
    }
1595
}
1596