Completed
Push — master ( 9c0a7d...c8b8f5 )
by Grégoire
03:08
created

CRUDController::aclAction()   B

Complexity

Conditions 8
Paths 12

Size

Total Lines 67

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 67
rs 7.4755
c 0
b 0
f 0
cc 8
nc 12
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Controller;
28
use Symfony\Component\DependencyInjection\ContainerInterface;
29
use Symfony\Component\Form\FormInterface;
30
use Symfony\Component\Form\FormRenderer;
31
use Symfony\Component\Form\FormView;
32
use Symfony\Component\HttpFoundation\JsonResponse;
33
use Symfony\Component\HttpFoundation\RedirectResponse;
34
use Symfony\Component\HttpFoundation\Request;
35
use Symfony\Component\HttpFoundation\Response;
36
use Symfony\Component\HttpKernel\Exception\HttpException;
37
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
38
use Symfony\Component\PropertyAccess\PropertyAccess;
39
use Symfony\Component\PropertyAccess\PropertyPath;
40
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
41
use Symfony\Component\Security\Csrf\CsrfToken;
42
43
/**
44
 * @author Thomas Rabaix <[email protected]>
45
 */
46
class CRUDController extends Controller
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Bundle\Framework...e\Controller\Controller has been deprecated with message: since Symfony 4.2, use "Symfony\Bundle\FrameworkBundle\Controller\AbstractController" instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

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

Loading history...
47
{
48
    /**
49
     * The related Admin class.
50
     *
51
     * @var AdminInterface
52
     */
53
    protected $admin;
54
55
    /**
56
     * The template registry of the related Admin class.
57
     *
58
     * @var TemplateRegistryInterface
59
     */
60
    private $templateRegistry;
61
62
    public function setContainer(ContainerInterface $container = null): void
63
    {
64
        $this->container = $container;
65
66
        $this->configure();
67
    }
68
69
    /**
70
     * Renders a view while passing mandatory parameters on to the template.
71
     *
72
     * @param string $view The view name
73
     *
74
     * @return Response A Response instance
75
     */
76
    public function renderWithExtraParams($view, array $parameters = [], Response $response = null)
77
    {
78
        if (!$this->isXmlHttpRequest()) {
79
            $parameters['breadcrumbs_builder'] = $this->get('sonata.admin.breadcrumbs_builder');
80
        }
81
        $parameters['admin'] = $parameters['admin'] ??
82
            $this->admin;
83
84
        $parameters['base_template'] = $parameters['base_template'] ??
85
            $this->getBaseTemplate();
86
87
        $parameters['admin_pool'] = $this->get('sonata.admin.pool');
88
89
        return parent::render($view, $parameters, $response);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (render() instead of renderWithExtraParams()). Are you sure this is correct? If so, you might want to change this to $this->render().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

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

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1417
                $this->admin->toString($object)
1418
            ));
1419
        }
1420
    }
1421
1422
    /**
1423
     * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
1424
     */
1425
    private function setFormTheme(FormView $formView, array $theme = null): void
1426
    {
1427
        $twig = $this->get('twig');
1428
1429
        $twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme);
1430
    }
1431
1432
    private function handleXmlHttpRequestErrorResponse(Request $request, FormInterface $form): JsonResponse
1433
    {
1434
        if (!\in_array('application/json', $request->getAcceptableContentTypes(), true)) {
1435
            return $this->renderJson([], Response::HTTP_NOT_ACCEPTABLE);
1436
        }
1437
1438
        $errors = [];
1439
        foreach ($form->getErrors(true) as $error) {
1440
            $errors[] = $error->getMessage();
1441
        }
1442
1443
        return $this->renderJson([
1444
            'result' => 'error',
1445
            'errors' => $errors,
1446
        ], Response::HTTP_BAD_REQUEST);
1447
    }
1448
1449
    /**
1450
     * @param object $object
1451
     */
1452
    private function handleXmlHttpRequestSuccessResponse(Request $request, $object): JsonResponse
1453
    {
1454
        if (!\in_array('application/json', $request->getAcceptableContentTypes(), true)) {
1455
            return $this->renderJson([], Response::HTTP_NOT_ACCEPTABLE);
1456
        }
1457
1458
        return $this->renderJson([
1459
            'result' => 'ok',
1460
            'objectId' => $this->admin->getNormalizedIdentifier($object),
1461
            'objectName' => $this->escapeHtml($this->admin->toString($object)),
1462
        ], Response::HTTP_OK);
1463
    }
1464
}
1465