Completed
Push — 3.x ( 4d66e8...5f8ed0 )
by Grégoire
03:00
created

CRUDController::historyAction()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

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

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1053
                    $this->addFlash(
1054
                        'sonata_flash_success',
1055
                        $this->trans('flash_acl_edit_success', [], 'SonataAdminBundle')
1056
                    );
1057
1058
                    return new RedirectResponse($this->admin->generateObjectUrl('acl', $object));
1059
                }
1060
            }
1061
        }
1062
1063
        // NEXT_MAJOR: Remove this line and use commented line below it instead
1064
        $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...
1065
        // $template = $this->templateRegistry->getTemplate('acl');
1066
1067
        return $this->renderWithExtraParams($template, [
1068
            'action' => 'acl',
1069
            'permissions' => $adminObjectAclData->getUserPermissions(),
1070
            'object' => $object,
1071
            'users' => $aclUsers,
1072
            'roles' => $aclRoles,
1073
            'aclUsersForm' => $aclUsersForm->createView(),
1074
            'aclRolesForm' => $aclRolesForm->createView(),
1075
        ], null);
1076
    }
1077
1078
    /**
1079
     * @return Request
1080
     */
1081
    public function getRequest()
1082
    {
1083
        return $this->container->get('request_stack')->getCurrentRequest();
1084
    }
1085
1086
    /**
1087
     * Gets a container configuration parameter by its name.
1088
     *
1089
     * @param string $name The parameter name
1090
     *
1091
     * @return mixed
1092
     */
1093
    protected function getParameter($name)
1094
    {
1095
        return $this->container->getParameter($name);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Container\ContainerInterface as the method getParameter() does only exist in the following implementations of said interface: Symfony\Bundle\FrameworkBundle\Test\TestContainer, Symfony\Component\Depend...urationContainerBuilder, Symfony\Component\DependencyInjection\Container, Symfony\Component\Depend...ection\ContainerBuilder.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1096
    }
1097
1098
    /**
1099
     * Render JSON.
1100
     *
1101
     * @param mixed $data
1102
     * @param int   $status
1103
     * @param array $headers
1104
     *
1105
     * @return JsonResponse with json encoded data
1106
     */
1107
    protected function renderJson($data, $status = Response::HTTP_OK, $headers = [])
1108
    {
1109
        return new JsonResponse($data, $status, $headers);
1110
    }
1111
1112
    /**
1113
     * Returns true if the request is a XMLHttpRequest.
1114
     *
1115
     * @return bool True if the request is an XMLHttpRequest, false otherwise
1116
     */
1117
    protected function isXmlHttpRequest()
1118
    {
1119
        $request = $this->getRequest();
1120
1121
        return $request->isXmlHttpRequest() || $request->get('_xml_http_request');
1122
    }
1123
1124
    /**
1125
     * Returns the correct RESTful verb, given either by the request itself or
1126
     * via the "_method" parameter.
1127
     *
1128
     * @return string HTTP method, either
1129
     */
1130
    protected function getRestMethod()
1131
    {
1132
        $request = $this->getRequest();
1133
1134
        if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) {
1135
            return $request->getMethod();
1136
        }
1137
1138
        return $request->request->get('_method');
1139
    }
1140
1141
    /**
1142
     * Contextualize the admin class depends on the current request.
1143
     *
1144
     * @throws \RuntimeException
1145
     */
1146
    protected function configure()
1147
    {
1148
        $request = $this->getRequest();
1149
1150
        $adminCode = $request->get('_sonata_admin');
1151
1152
        if (!$adminCode) {
1153
            throw new \RuntimeException(sprintf(
1154
                'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`',
1155
                static::class,
1156
                $request->get('_route')
1157
            ));
1158
        }
1159
1160
        $this->admin = $this->container->get('sonata.admin.pool')->getAdminByAdminCode($adminCode);
1161
1162
        if (!$this->admin) {
1163
            throw new \RuntimeException(sprintf(
1164
                'Unable to find the admin class related to the current controller (%s)',
1165
                static::class
1166
            ));
1167
        }
1168
1169
        $this->templateRegistry = $this->container->get($this->admin->getCode().'.template_registry');
1170
        if (!$this->templateRegistry instanceof TemplateRegistryInterface) {
1171
            throw new \RuntimeException(sprintf(
1172
                'Unable to find the template registry related to the current admin (%s)',
1173
                $this->admin->getCode()
1174
            ));
1175
        }
1176
1177
        $rootAdmin = $this->admin;
1178
1179
        while ($rootAdmin->isChild()) {
1180
            $rootAdmin->setCurrentChild(true);
1181
            $rootAdmin = $rootAdmin->getParent();
1182
        }
1183
1184
        $rootAdmin->setRequest($request);
1185
1186
        if ($request->get('uniqid')) {
1187
            $this->admin->setUniqid($request->get('uniqid'));
1188
        }
1189
    }
1190
1191
    /**
1192
     * Proxy for the logger service of the container.
1193
     * If no such service is found, a NullLogger is returned.
1194
     *
1195
     * @return LoggerInterface
1196
     */
1197
    protected function getLogger()
1198
    {
1199
        if ($this->container->has('logger')) {
1200
            $logger = $this->container->get('logger');
1201
            \assert($logger instanceof LoggerInterface);
1202
1203
            return $logger;
1204
        }
1205
1206
        return new NullLogger();
1207
    }
1208
1209
    /**
1210
     * Returns the base template name.
1211
     *
1212
     * @return string The template name
1213
     */
1214
    protected function getBaseTemplate()
1215
    {
1216
        if ($this->isXmlHttpRequest()) {
1217
            // NEXT_MAJOR: Remove this line and use commented line below it instead
1218
            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...
1219
            // return $this->templateRegistry->getTemplate('ajax');
1220
        }
1221
1222
        // NEXT_MAJOR: Remove this line and use commented line below it instead
1223
        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...
1224
        // return $this->templateRegistry->getTemplate('layout');
1225
    }
1226
1227
    /**
1228
     * @throws \Exception
1229
     */
1230
    protected function handleModelManagerException(\Exception $e)
1231
    {
1232
        if ($this->get('kernel')->isDebug()) {
1233
            throw $e;
1234
        }
1235
1236
        $context = ['exception' => $e];
1237
        if ($e->getPrevious()) {
1238
            $context['previous_exception_message'] = $e->getPrevious()->getMessage();
1239
        }
1240
        $this->getLogger()->error($e->getMessage(), $context);
1241
    }
1242
1243
    /**
1244
     * Redirect the user depend on this choice.
1245
     *
1246
     * @param object $object
1247
     *
1248
     * @return RedirectResponse
1249
     */
1250
    protected function redirectTo($object)
1251
    {
1252
        $request = $this->getRequest();
1253
1254
        $url = false;
1255
1256
        if (null !== $request->get('btn_update_and_list')) {
1257
            return $this->redirectToList();
1258
        }
1259
        if (null !== $request->get('btn_create_and_list')) {
1260
            return $this->redirectToList();
1261
        }
1262
1263
        if (null !== $request->get('btn_create_and_create')) {
1264
            $params = [];
1265
            if ($this->admin->hasActiveSubClass()) {
1266
                $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...
1267
            }
1268
            $url = $this->admin->generateUrl('create', $params);
1269
        }
1270
1271
        if ('DELETE' === $this->getRestMethod()) {
1272
            return $this->redirectToList();
1273
        }
1274
1275
        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...
1276
            foreach (['edit', 'show'] as $route) {
1277
                if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route, $object)) {
1278
                    $url = $this->admin->generateObjectUrl(
1279
                        $route,
1280
                        $object,
1281
                        $this->getSelectedTab($request)
1282
                    );
1283
1284
                    break;
1285
                }
1286
            }
1287
        }
1288
1289
        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...
1290
            return $this->redirectToList();
1291
        }
1292
1293
        return new RedirectResponse($url);
1294
    }
1295
1296
    /**
1297
     * Redirects the user to the list view.
1298
     *
1299
     * @return RedirectResponse
1300
     */
1301
    final protected function redirectToList()
1302
    {
1303
        $parameters = [];
1304
1305
        if ($filter = $this->admin->getFilterParameters()) {
1306
            $parameters['filter'] = $filter;
1307
        }
1308
1309
        return $this->redirect($this->admin->generateUrl('list', $parameters));
1310
    }
1311
1312
    /**
1313
     * Returns true if the preview is requested to be shown.
1314
     *
1315
     * @return bool
1316
     */
1317
    protected function isPreviewRequested()
1318
    {
1319
        $request = $this->getRequest();
1320
1321
        return null !== $request->get('btn_preview');
1322
    }
1323
1324
    /**
1325
     * Returns true if the preview has been approved.
1326
     *
1327
     * @return bool
1328
     */
1329
    protected function isPreviewApproved()
1330
    {
1331
        $request = $this->getRequest();
1332
1333
        return null !== $request->get('btn_preview_approve');
1334
    }
1335
1336
    /**
1337
     * Returns true if the request is in the preview workflow.
1338
     *
1339
     * That means either a preview is requested or the preview has already been shown
1340
     * and it got approved/declined.
1341
     *
1342
     * @return bool
1343
     */
1344
    protected function isInPreviewMode()
1345
    {
1346
        return $this->admin->supportsPreviewMode()
1347
        && ($this->isPreviewRequested()
1348
            || $this->isPreviewApproved()
1349
            || $this->isPreviewDeclined());
1350
    }
1351
1352
    /**
1353
     * Returns true if the preview has been declined.
1354
     *
1355
     * @return bool
1356
     */
1357
    protected function isPreviewDeclined()
1358
    {
1359
        $request = $this->getRequest();
1360
1361
        return null !== $request->get('btn_preview_decline');
1362
    }
1363
1364
    /**
1365
     * Gets ACL users.
1366
     *
1367
     * @return \Traversable
1368
     */
1369
    protected function getAclUsers()
1370
    {
1371
        $aclUsers = [];
1372
1373
        $userManagerServiceName = $this->container->getParameter('sonata.admin.security.acl_user_manager');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Container\ContainerInterface as the method getParameter() does only exist in the following implementations of said interface: Symfony\Bundle\FrameworkBundle\Test\TestContainer, Symfony\Component\Depend...urationContainerBuilder, Symfony\Component\DependencyInjection\Container, Symfony\Component\Depend...ection\ContainerBuilder.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1374
        if (null !== $userManagerServiceName && $this->has($userManagerServiceName)) {
1375
            $userManager = $this->get($userManagerServiceName);
1376
1377
            if (method_exists($userManager, 'findUsers')) {
1378
                $aclUsers = $userManager->findUsers();
1379
            }
1380
        }
1381
1382
        return \is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
1383
    }
1384
1385
    /**
1386
     * Gets ACL roles.
1387
     *
1388
     * @return \Traversable
1389
     */
1390
    protected function getAclRoles()
1391
    {
1392
        $aclRoles = [];
1393
        $roleHierarchy = $this->container->getParameter('security.role_hierarchy.roles');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Container\ContainerInterface as the method getParameter() does only exist in the following implementations of said interface: Symfony\Bundle\FrameworkBundle\Test\TestContainer, Symfony\Component\Depend...urationContainerBuilder, Symfony\Component\DependencyInjection\Container, Symfony\Component\Depend...ection\ContainerBuilder.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1394
        $pool = $this->container->get('sonata.admin.pool');
1395
1396
        foreach ($pool->getAdminServiceIds() as $id) {
1397
            try {
1398
                $admin = $pool->getInstance($id);
1399
            } catch (\Exception $e) {
1400
                continue;
1401
            }
1402
1403
            $baseRole = $admin->getSecurityHandler()->getBaseRole($admin);
1404
            foreach ($admin->getSecurityInformation() as $role => $permissions) {
1405
                $role = sprintf($baseRole, $role);
1406
                $aclRoles[] = $role;
1407
            }
1408
        }
1409
1410
        foreach ($roleHierarchy as $name => $roles) {
1411
            $aclRoles[] = $name;
1412
            $aclRoles = array_merge($aclRoles, $roles);
1413
        }
1414
1415
        $aclRoles = array_unique($aclRoles);
1416
1417
        return \is_array($aclRoles) ? new \ArrayIterator($aclRoles) : $aclRoles;
1418
    }
1419
1420
    /**
1421
     * Validate CSRF token for action without form.
1422
     *
1423
     * @param string $intention
1424
     *
1425
     * @throws HttpException
1426
     */
1427
    protected function validateCsrfToken($intention)
1428
    {
1429
        if (false === $this->admin->getFormBuilder()->getOption('csrf_protection')) {
1430
            return;
1431
        }
1432
1433
        $request = $this->getRequest();
1434
        $token = $request->get('_sonata_csrf_token');
1435
1436
        if ($this->container->has('security.csrf.token_manager')) {
1437
            $valid = $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($intention, $token));
1438
        } else {
1439
            return;
1440
        }
1441
1442
        if (!$valid) {
1443
            throw new HttpException(Response::HTTP_BAD_REQUEST, 'The csrf token is not valid, CSRF attack?');
1444
        }
1445
    }
1446
1447
    /**
1448
     * Escape string for html output.
1449
     *
1450
     * @param string $s
1451
     *
1452
     * @return string
1453
     */
1454
    protected function escapeHtml($s)
1455
    {
1456
        return htmlspecialchars((string) $s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1457
    }
1458
1459
    /**
1460
     * Get CSRF token.
1461
     *
1462
     * @param string $intention
1463
     *
1464
     * @return string|false
1465
     */
1466
    protected function getCsrfToken($intention)
1467
    {
1468
        if ($this->container->has('security.csrf.token_manager')) {
1469
            return $this->container->get('security.csrf.token_manager')->getToken($intention)->getValue();
1470
        }
1471
1472
        return false;
1473
    }
1474
1475
    /**
1476
     * This method can be overloaded in your custom CRUD controller.
1477
     * It's called from createAction.
1478
     *
1479
     * @param object $object
1480
     *
1481
     * @return Response|null
1482
     */
1483
    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...
1484
    {
1485
        return null;
1486
    }
1487
1488
    /**
1489
     * This method can be overloaded in your custom CRUD controller.
1490
     * It's called from editAction.
1491
     *
1492
     * @param object $object
1493
     *
1494
     * @return Response|null
1495
     */
1496
    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...
1497
    {
1498
        return null;
1499
    }
1500
1501
    /**
1502
     * This method can be overloaded in your custom CRUD controller.
1503
     * It's called from deleteAction.
1504
     *
1505
     * @param object $object
1506
     *
1507
     * @return Response|null
1508
     */
1509
    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...
1510
    {
1511
        return null;
1512
    }
1513
1514
    /**
1515
     * This method can be overloaded in your custom CRUD controller.
1516
     * It's called from showAction.
1517
     *
1518
     * @param object $object
1519
     *
1520
     * @return Response|null
1521
     */
1522
    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...
1523
    {
1524
        return null;
1525
    }
1526
1527
    /**
1528
     * This method can be overloaded in your custom CRUD controller.
1529
     * It's called from listAction.
1530
     *
1531
     * @return Response|null
1532
     */
1533
    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...
1534
    {
1535
        return null;
1536
    }
1537
1538
    /**
1539
     * Translate a message id.
1540
     *
1541
     * @param string $id
1542
     * @param string $domain
1543
     * @param string $locale
1544
     *
1545
     * @return string translated string
1546
     */
1547
    final protected function trans($id, array $parameters = [], $domain = null, $locale = null)
1548
    {
1549
        $domain = $domain ?: $this->admin->getTranslationDomain();
1550
1551
        return $this->get('translator')->trans($id, $parameters, $domain, $locale);
1552
    }
1553
1554
    private function getSelectedTab(Request $request): array
1555
    {
1556
        return array_filter(['_tab' => $request->request->get('_tab')]);
1557
    }
1558
1559
    private function checkParentChildAssociation(Request $request, $object): void
1560
    {
1561
        if (!($parentAdmin = $this->admin->getParent())) {
1562
            return;
1563
        }
1564
1565
        // NEXT_MAJOR: remove this check
1566
        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...
1567
            return;
1568
        }
1569
1570
        $parentId = $request->get($parentAdmin->getIdParameter());
1571
1572
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
1573
        $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...
1574
1575
        if ($parentAdmin->getObject($parentId) !== $propertyAccessor->getValue($object, $propertyPath)) {
1576
            // NEXT_MAJOR: make this exception
1577
            @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...
1578
                "Accessing a child that isn't connected to a given parent is"
1579
                ." deprecated since sonata-project/admin-bundle 3.34 and won't be allowed in 4.0.",
1580
                E_USER_DEPRECATED
1581
            );
1582
        }
1583
    }
1584
1585
    /**
1586
     * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
1587
     */
1588
    private function setFormTheme(FormView $formView, array $theme = null): void
1589
    {
1590
        $twig = $this->get('twig');
1591
1592
        $twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme);
1593
    }
1594
1595
    private function handleXmlHttpRequestErrorResponse(Request $request, FormInterface $form): ?JsonResponse
1596
    {
1597
        if ('application/json' !== $request->headers->get('Accept')) {
1598
            @trigger_error('In next major version response will return 406 NOT ACCEPTABLE without `Accept: application/json`', 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...
1599
1600
            return null;
1601
        }
1602
1603
        $errors = [];
1604
        foreach ($form->getErrors(true) as $error) {
1605
            $errors[] = $error->getMessage();
1606
        }
1607
1608
        return $this->renderJson([
1609
            'result' => 'error',
1610
            'errors' => $errors,
1611
        ], Response::HTTP_BAD_REQUEST);
1612
    }
1613
1614
    /**
1615
     * @param object $object
1616
     */
1617
    private function handleXmlHttpRequestSuccessResponse(Request $request, $object): JsonResponse
1618
    {
1619
        if ('application/json' !== $request->headers->get('Accept')) {
1620
            @trigger_error('In next major version response will return 406 NOT ACCEPTABLE without `Accept: application/json`', 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...
1621
        }
1622
1623
        return $this->renderJson([
1624
            'result' => 'ok',
1625
            'objectId' => $this->admin->getNormalizedIdentifier($object),
1626
            'objectName' => $this->escapeHtml($this->admin->toString($object)),
1627
        ], Response::HTTP_OK);
1628
    }
1629
}
1630