Completed
Pull Request — 3.x (#4772)
by Grégoire
03:39
created

CRUDController::isInPreviewMode()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.2
c 0
b 0
f 0
cc 4
eloc 5
nc 4
nop 0
1
<?php
2
3
/*
4
 * This file is part of the Sonata Project package.
5
 *
6
 * (c) Thomas Rabaix <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sonata\AdminBundle\Controller;
13
14
use Doctrine\Common\Inflector\Inflector;
15
use Psr\Log\LoggerInterface;
16
use Psr\Log\NullLogger;
17
use Sonata\AdminBundle\Admin\AdminInterface;
18
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
19
use Sonata\AdminBundle\Exception\LockException;
20
use Sonata\AdminBundle\Exception\ModelManagerException;
21
use Sonata\AdminBundle\Util\AdminObjectAclData;
22
use Sonata\AdminBundle\Util\AdminObjectAclManipulator;
23
use Symfony\Bridge\Twig\AppVariable;
24
use Symfony\Bridge\Twig\Command\DebugCommand;
25
use Symfony\Bridge\Twig\Extension\FormExtension;
26
use Symfony\Bridge\Twig\Form\TwigRenderer;
27
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
28
use Symfony\Component\DependencyInjection\ContainerInterface;
29
use Symfony\Component\Form\FormRenderer;
30
use Symfony\Component\Form\FormView;
31
use Symfony\Component\HttpFoundation\JsonResponse;
32
use Symfony\Component\HttpFoundation\RedirectResponse;
33
use Symfony\Component\HttpFoundation\Request;
34
use Symfony\Component\HttpFoundation\Response;
35
use Symfony\Component\HttpKernel\Exception\HttpException;
36
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
37
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
38
use Symfony\Component\Security\Csrf\CsrfToken;
39
40
/**
41
 * @author Thomas Rabaix <[email protected]>
42
 */
43
class CRUDController extends Controller
44
{
45
    /**
46
     * The related Admin class.
47
     *
48
     * @var AdminInterface
49
     */
50
    protected $admin;
51
52
    /**
53
     * Sets the Container associated with this Controller.
54
     *
55
     * @param ContainerInterface $container A ContainerInterface instance
56
     */
57
    public function setContainer(ContainerInterface $container = null)
58
    {
59
        $this->container = $container;
60
61
        $this->configure();
62
    }
63
64
    public function __call($method, $arguments)
65
    {
66
        if ('render' === $method) {
67
            if (count($arguments) < 1) {
68
                throw new \LogicException(sprintf(
69
                    '%s::%s requires at least one argument',
70
                    __CLASS__,
71
                    $method
72
                ));
73
            }
74
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
75
                'Calling "%s::render" is deprecated since 3.x and will no longer work as expected in 4.0.'.
76
                ' Call "%s::renderWithExtraParams" instead.',
77
                __CLASS__,
78
                __CLASS__
79
            ), E_USER_DEPRECATED);
80
            $arguments += [null, [], null];
81
            return $this->renderWithExtraParams($arguments[0], $arguments[1], $arguments[2]);
82
        }
83
84
        throw new \LogicException(sprintf('Call to undefined method %s::%s', __CLASS__, $method));
85
    }
86
87
    /**
88
     * Adds mandatory parameters before calling render().
89
     */
90
    public function renderWithExtraParams($view, array $parameters = [], Response $response = null)
91
    {
92
        if (!$this->isXmlHttpRequest()) {
93
            $parameters['breadcrumbs_builder'] = $this->get('sonata.admin.breadcrumbs_builder');
94
        }
95
        $parameters['admin'] = isset($parameters['admin']) ?
96
            $parameters['admin'] :
97
            $this->admin;
98
99
        $parameters['base_template'] = isset($parameters['base_template']) ?
100
            $parameters['base_template'] :
101
            $this->getBaseTemplate();
102
103
        $parameters['admin_pool'] = $this->get('sonata.admin.pool');
104
105
        return $this->render($view, $parameters, $response);
106
    }
107
108
    /**
109
     * List action.
110
     *
111
     * @return Response
112
     *
113
     * @throws AccessDeniedException If access is not granted
114
     */
115
    public function listAction()
116
    {
117
        $request = $this->getRequest();
118
119
        $this->admin->checkAccess('list');
120
121
        $preResponse = $this->preList($request);
122
        if (null !== $preResponse) {
123
            return $preResponse;
124
        }
125
126
        if ($listMode = $request->get('_list_mode')) {
127
            $this->admin->setListMode($listMode);
128
        }
129
130
        $datagrid = $this->admin->getDatagrid();
131
        $formView = $datagrid->getForm()->createView();
132
133
        // set the theme for the current Admin Form
134
        $this->setFormTheme($formView, $this->admin->getFilterTheme());
0 ignored issues
show
Documentation introduced by
$this->admin->getFilterTheme() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
135
136
        return $this->renderWithExtraParams($this->admin->getTemplate('list'), [
137
            'action' => 'list',
138
            'form' => $formView,
139
            'datagrid' => $datagrid,
140
            'csrf_token' => $this->getCsrfToken('sonata.batch'),
141
            'export_formats' => $this->has('sonata.admin.admin_exporter') ?
142
                $this->get('sonata.admin.admin_exporter')->getAvailableFormats($this->admin) :
143
                $this->admin->getExportFormats(),
144
        ], null);
145
    }
146
147
    /**
148
     * Execute a batch delete.
149
     *
150
     * @param ProxyQueryInterface $query
151
     *
152
     * @return RedirectResponse
153
     *
154
     * @throws AccessDeniedException If access is not granted
155
     */
156
    public function batchActionDelete(ProxyQueryInterface $query)
157
    {
158
        $this->admin->checkAccess('batchDelete');
159
160
        $modelManager = $this->admin->getModelManager();
161
162
        try {
163
            $modelManager->batchDelete($this->admin->getClass(), $query);
164
            $this->addFlash(
165
                'sonata_flash_success',
166
                $this->trans('flash_batch_delete_success', [], 'SonataAdminBundle')
167
            );
168
        } catch (ModelManagerException $e) {
169
            $this->handleModelManagerException($e);
170
            $this->addFlash(
171
                'sonata_flash_error',
172
                $this->trans('flash_batch_delete_error', [], 'SonataAdminBundle')
173
            );
174
        }
175
176
        return new RedirectResponse($this->admin->generateUrl(
177
            'list',
178
            ['filter' => $this->admin->getFilterParameters()]
179
        ));
180
    }
181
182
    /**
183
     * Delete action.
184
     *
185
     * @param int|string|null $id
186
     *
187
     * @return Response|RedirectResponse
188
     *
189
     * @throws NotFoundHttpException If the object does not exist
190
     * @throws AccessDeniedException If access is not granted
191
     */
192
    public function deleteAction($id)
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...
193
    {
194
        $request = $this->getRequest();
195
        $id = $request->get($this->admin->getIdParameter());
196
        $object = $this->admin->getObject($id);
197
198
        if (!$object) {
199
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
200
        }
201
202
        $this->admin->checkAccess('delete', $object);
203
204
        $preResponse = $this->preDelete($request, $object);
205
        if (null !== $preResponse) {
206
            return $preResponse;
207
        }
208
209
        if ('DELETE' == $this->getRestMethod()) {
210
            // check the csrf token
211
            $this->validateCsrfToken('sonata.delete');
212
213
            $objectName = $this->admin->toString($object);
214
215
            try {
216
                $this->admin->delete($object);
217
218
                if ($this->isXmlHttpRequest()) {
219
                    return $this->renderJson(['result' => 'ok'], 200, []);
220
                }
221
222
                $this->addFlash(
223
                    'sonata_flash_success',
224
                    $this->trans(
225
                        'flash_delete_success',
226
                        ['%name%' => $this->escapeHtml($objectName)],
227
                        'SonataAdminBundle'
228
                    )
229
                );
230
            } catch (ModelManagerException $e) {
231
                $this->handleModelManagerException($e);
232
233
                if ($this->isXmlHttpRequest()) {
234
                    return $this->renderJson(['result' => 'error'], 200, []);
235
                }
236
237
                $this->addFlash(
238
                    'sonata_flash_error',
239
                    $this->trans(
240
                        'flash_delete_error',
241
                        ['%name%' => $this->escapeHtml($objectName)],
242
                        'SonataAdminBundle'
243
                    )
244
                );
245
            }
246
247
            return $this->redirectTo($object);
248
        }
249
250
        return $this->renderWithExtraParams($this->admin->getTemplate('delete'), [
251
            'object' => $object,
252
            'action' => 'delete',
253
            'csrf_token' => $this->getCsrfToken('sonata.delete'),
254
        ], null);
255
    }
256
257
    /**
258
     * Edit action.
259
     *
260
     * @param int|string|null $id
261
     *
262
     * @return Response|RedirectResponse
263
     *
264
     * @throws NotFoundHttpException If the object does not exist
265
     * @throws AccessDeniedException If access is not granted
266
     */
267
    public function editAction($id = null)
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...
268
    {
269
        $request = $this->getRequest();
270
        // the key used to lookup the template
271
        $templateKey = 'edit';
272
273
        $id = $request->get($this->admin->getIdParameter());
274
        $existingObject = $this->admin->getObject($id);
275
276
        if (!$existingObject) {
277
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
278
        }
279
280
        $this->admin->checkAccess('edit', $existingObject);
281
282
        $preResponse = $this->preEdit($request, $existingObject);
283
        if (null !== $preResponse) {
284
            return $preResponse;
285
        }
286
287
        $this->admin->setSubject($existingObject);
288
        $objectId = $this->admin->getNormalizedIdentifier($existingObject);
289
290
        /** @var $form Form */
291
        $form = $this->admin->getForm();
292
        $form->setData($existingObject);
293
        $form->handleRequest($request);
294
295
        if ($form->isSubmitted()) {
296
            //TODO: remove this check for 4.0
297
            if (method_exists($this->admin, 'preValidate')) {
298
                $this->admin->preValidate($existingObject);
0 ignored issues
show
Bug introduced by
The method preValidate() does not exist on Sonata\AdminBundle\Admin\AdminInterface. Did you maybe mean validate()?

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...
299
            }
300
            $isFormValid = $form->isValid();
301
302
            // persist if the form was valid and if in preview mode the preview was approved
303
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
304
                $submittedObject = $form->getData();
305
                $this->admin->setSubject($submittedObject);
306
307
                try {
308
                    $existingObject = $this->admin->update($submittedObject);
309
310
                    if ($this->isXmlHttpRequest()) {
311
                        return $this->renderJson([
312
                            'result' => 'ok',
313
                            'objectId' => $objectId,
314
                            'objectName' => $this->escapeHtml($this->admin->toString($existingObject)),
315
                        ], 200, []);
316
                    }
317
318
                    $this->addFlash(
319
                        'sonata_flash_success',
320
                        $this->trans(
321
                            'flash_edit_success',
322
                            ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
323
                            'SonataAdminBundle'
324
                        )
325
                    );
326
327
                    // redirect to edit mode
328
                    return $this->redirectTo($existingObject);
329
                } catch (ModelManagerException $e) {
330
                    $this->handleModelManagerException($e);
331
332
                    $isFormValid = false;
333
                } catch (LockException $e) {
334
                    $this->addFlash('sonata_flash_error', $this->trans('flash_lock_error', [
335
                        '%name%' => $this->escapeHtml($this->admin->toString($existingObject)),
336
                        '%link_start%' => '<a href="'.$this->admin->generateObjectUrl('edit', $existingObject).'">',
337
                        '%link_end%' => '</a>',
338
                    ], 'SonataAdminBundle'));
339
                }
340
            }
341
342
            // show an error message if the form failed validation
343
            if (!$isFormValid) {
344
                if (!$this->isXmlHttpRequest()) {
345
                    $this->addFlash(
346
                        'sonata_flash_error',
347
                        $this->trans(
348
                            'flash_edit_error',
349
                            ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
350
                            'SonataAdminBundle'
351
                        )
352
                    );
353
                }
354
            } elseif ($this->isPreviewRequested()) {
355
                // enable the preview template if the form was valid and preview was requested
356
                $templateKey = 'preview';
357
                $this->admin->getShow();
358
            }
359
        }
360
361
        $formView = $form->createView();
362
        // set the theme for the current Admin Form
363
        $this->setFormTheme($formView, $this->admin->getFormTheme());
0 ignored issues
show
Documentation introduced by
$this->admin->getFormTheme() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
364
365
        return $this->renderWithExtraParams($this->admin->getTemplate($templateKey), [
366
            'action' => 'edit',
367
            'form' => $formView,
368
            'object' => $existingObject,
369
            'objectId' => $objectId,
370
        ], null);
371
    }
372
373
    /**
374
     * Batch action.
375
     *
376
     * @return Response|RedirectResponse
377
     *
378
     * @throws NotFoundHttpException If the HTTP method is not POST
379
     * @throws \RuntimeException     If the batch action is not defined
380
     */
381
    public function batchAction()
382
    {
383
        $request = $this->getRequest();
384
        $restMethod = $this->getRestMethod();
385
386
        if ('POST' !== $restMethod) {
387
            throw $this->createNotFoundException(sprintf('Invalid request type "%s", POST expected', $restMethod));
388
        }
389
390
        // check the csrf token
391
        $this->validateCsrfToken('sonata.batch');
392
393
        $confirmation = $request->get('confirmation', false);
394
395
        if ($data = json_decode($request->get('data'), true)) {
396
            $action = $data['action'];
397
            $idx = $data['idx'];
398
            $allElements = $data['all_elements'];
399
            $request->request->replace(array_merge($request->request->all(), $data));
400
        } else {
401
            $request->request->set('idx', $request->get('idx', []));
402
            $request->request->set('all_elements', $request->get('all_elements', false));
403
404
            $action = $request->get('action');
405
            $idx = $request->get('idx');
406
            $allElements = $request->get('all_elements');
407
            $data = $request->request->all();
408
409
            unset($data['_sonata_csrf_token']);
410
        }
411
412
        // NEXT_MAJOR: Remove reflection check.
413
        $reflector = new \ReflectionMethod($this->admin, 'getBatchActions');
414
        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...
415
            @trigger_error('Override Sonata\AdminBundle\Admin\AbstractAdmin::getBatchActions method'
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...
416
                .' is deprecated since version 3.2.'
417
                .' Use Sonata\AdminBundle\Admin\AbstractAdmin::configureBatchActions instead.'
418
                .' The method will be final in 4.0.', E_USER_DEPRECATED
419
            );
420
        }
421
        $batchActions = $this->admin->getBatchActions();
422
        if (!array_key_exists($action, $batchActions)) {
423
            throw new \RuntimeException(sprintf('The `%s` batch action is not defined', $action));
424
        }
425
426
        $camelizedAction = Inflector::classify($action);
427
        $isRelevantAction = sprintf('batchAction%sIsRelevant', $camelizedAction);
428
429
        if (method_exists($this, $isRelevantAction)) {
430
            $nonRelevantMessage = call_user_func([$this, $isRelevantAction], $idx, $allElements, $request);
431
        } else {
432
            $nonRelevantMessage = 0 != count($idx) || $allElements; // at least one item is selected
433
        }
434
435
        if (!$nonRelevantMessage) { // default non relevant message (if false of null)
436
            $nonRelevantMessage = 'flash_batch_empty';
437
        }
438
439
        $datagrid = $this->admin->getDatagrid();
440
        $datagrid->buildPager();
441
442
        if (true !== $nonRelevantMessage) {
443
            $this->addFlash(
444
                'sonata_flash_info',
445
                $this->trans($nonRelevantMessage, [], 'SonataAdminBundle')
446
            );
447
448
            return new RedirectResponse(
449
                $this->admin->generateUrl(
450
                    'list',
451
                    ['filter' => $this->admin->getFilterParameters()]
452
                )
453
            );
454
        }
455
456
        $askConfirmation = isset($batchActions[$action]['ask_confirmation']) ?
457
            $batchActions[$action]['ask_confirmation'] :
458
            true;
459
460
        if ($askConfirmation && 'ok' != $confirmation) {
461
            $actionLabel = $batchActions[$action]['label'];
462
            $batchTranslationDomain = isset($batchActions[$action]['translation_domain']) ?
463
                $batchActions[$action]['translation_domain'] :
464
                $this->admin->getTranslationDomain();
465
466
            $formView = $datagrid->getForm()->createView();
467
            $this->setFormTheme($formView, $this->admin->getFilterTheme());
0 ignored issues
show
Documentation introduced by
$this->admin->getFilterTheme() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
468
469
            return $this->renderWithExtraParams($this->admin->getTemplate('batch_confirmation'), [
470
                'action' => 'list',
471
                'action_label' => $actionLabel,
472
                'batch_translation_domain' => $batchTranslationDomain,
473
                'datagrid' => $datagrid,
474
                'form' => $formView,
475
                'data' => $data,
476
                'csrf_token' => $this->getCsrfToken('sonata.batch'),
477
            ], null);
478
        }
479
480
        // execute the action, batchActionXxxxx
481
        $finalAction = sprintf('batchAction%s', $camelizedAction);
482
        if (!method_exists($this, $finalAction)) {
483
            throw new \RuntimeException(sprintf('A `%s::%s` method must be callable', get_class($this), $finalAction));
484
        }
485
486
        $query = $datagrid->getQuery();
487
488
        $query->setFirstResult(null);
489
        $query->setMaxResults(null);
490
491
        $this->admin->preBatchAction($action, $query, $idx, $allElements);
492
493
        if (count($idx) > 0) {
494
            $this->admin->getModelManager()->addIdentifiersToQuery($this->admin->getClass(), $query, $idx);
495
        } elseif (!$allElements) {
496
            $this->addFlash(
497
                'sonata_flash_info',
498
                $this->trans('flash_batch_no_elements_processed', [], 'SonataAdminBundle')
499
            );
500
501
            return new RedirectResponse(
502
                $this->admin->generateUrl('list', [
503
                    'filter' => $this->admin->getFilterParameters(),
504
                ])
505
            );
506
        }
507
508
        return call_user_func([$this, $finalAction], $query, $request);
509
    }
510
511
    /**
512
     * Create action.
513
     *
514
     * @return Response
515
     *
516
     * @throws AccessDeniedException If access is not granted
517
     */
518
    public function createAction()
519
    {
520
        $request = $this->getRequest();
521
        // the key used to lookup the template
522
        $templateKey = 'edit';
523
524
        $this->admin->checkAccess('create');
525
526
        $class = new \ReflectionClass($this->admin->hasActiveSubClass() ? $this->admin->getActiveSubClass() : $this->admin->getClass());
527
528
        if ($class->isAbstract()) {
529
            return $this->renderWithExtraParams(
530
                'SonataAdminBundle:CRUD:select_subclass.html.twig',
531
                [
532
                    'base_template' => $this->getBaseTemplate(),
533
                    'admin' => $this->admin,
534
                    'action' => 'create',
535
                ],
536
                null
537
            );
538
        }
539
540
        $newObject = $this->admin->getNewInstance();
541
542
        $preResponse = $this->preCreate($request, $newObject);
543
        if (null !== $preResponse) {
544
            return $preResponse;
545
        }
546
547
        $this->admin->setSubject($newObject);
548
549
        /** @var $form \Symfony\Component\Form\Form */
550
        $form = $this->admin->getForm();
551
        $form->setData($newObject);
552
        $form->handleRequest($request);
553
554
        if ($form->isSubmitted()) {
555
            //TODO: remove this check for 4.0
556
            if (method_exists($this->admin, 'preValidate')) {
557
                $this->admin->preValidate($newObject);
0 ignored issues
show
Bug introduced by
The method preValidate() does not exist on Sonata\AdminBundle\Admin\AdminInterface. Did you maybe mean validate()?

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...
558
            }
559
            $isFormValid = $form->isValid();
560
561
            // persist if the form was valid and if in preview mode the preview was approved
562
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
563
                $submittedObject = $form->getData();
564
                $this->admin->setSubject($submittedObject);
565
                $this->admin->checkAccess('create', $submittedObject);
566
567
                try {
568
                    $newObject = $this->admin->create($submittedObject);
569
570
                    if ($this->isXmlHttpRequest()) {
571
                        return $this->renderJson([
572
                            'result' => 'ok',
573
                            'objectId' => $this->admin->getNormalizedIdentifier($newObject),
574
                        ], 200, []);
575
                    }
576
577
                    $this->addFlash(
578
                        'sonata_flash_success',
579
                        $this->trans(
580
                            'flash_create_success',
581
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
582
                            'SonataAdminBundle'
583
                        )
584
                    );
585
586
                    // redirect to edit mode
587
                    return $this->redirectTo($newObject);
588
                } catch (ModelManagerException $e) {
589
                    $this->handleModelManagerException($e);
590
591
                    $isFormValid = false;
592
                }
593
            }
594
595
            // show an error message if the form failed validation
596
            if (!$isFormValid) {
597
                if (!$this->isXmlHttpRequest()) {
598
                    $this->addFlash(
599
                        'sonata_flash_error',
600
                        $this->trans(
601
                            'flash_create_error',
602
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
603
                            'SonataAdminBundle'
604
                        )
605
                    );
606
                }
607
            } elseif ($this->isPreviewRequested()) {
608
                // pick the preview template if the form was valid and preview was requested
609
                $templateKey = 'preview';
610
                $this->admin->getShow();
611
            }
612
        }
613
614
        $formView = $form->createView();
615
        // set the theme for the current Admin Form
616
        $this->setFormTheme($formView, $this->admin->getFormTheme());
0 ignored issues
show
Documentation introduced by
$this->admin->getFormTheme() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
617
618
        return $this->renderWithExtraParams($this->admin->getTemplate($templateKey), [
619
            'action' => 'create',
620
            'form' => $formView,
621
            'object' => $newObject,
622
            'objectId' => null,
623
        ], null);
624
    }
625
626
    /**
627
     * Show action.
628
     *
629
     * @param int|string|null $id
630
     *
631
     * @return Response
632
     *
633
     * @throws NotFoundHttpException If the object does not exist
634
     * @throws AccessDeniedException If access is not granted
635
     */
636
    public function showAction($id = null)
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...
637
    {
638
        $request = $this->getRequest();
639
        $id = $request->get($this->admin->getIdParameter());
640
641
        $object = $this->admin->getObject($id);
642
643
        if (!$object) {
644
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
645
        }
646
647
        $this->admin->checkAccess('show', $object);
648
649
        $preResponse = $this->preShow($request, $object);
650
        if (null !== $preResponse) {
651
            return $preResponse;
652
        }
653
654
        $this->admin->setSubject($object);
655
656
        return $this->renderWithExtraParams($this->admin->getTemplate('show'), [
657
            'action' => 'show',
658
            'object' => $object,
659
            'elements' => $this->admin->getShow(),
660
        ], null);
661
    }
662
663
    /**
664
     * Show history revisions for object.
665
     *
666
     * @param int|string|null $id
667
     *
668
     * @return Response
669
     *
670
     * @throws AccessDeniedException If access is not granted
671
     * @throws NotFoundHttpException If the object does not exist or the audit reader is not available
672
     */
673
    public function historyAction($id = null)
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...
674
    {
675
        $request = $this->getRequest();
676
        $id = $request->get($this->admin->getIdParameter());
677
678
        $object = $this->admin->getObject($id);
679
680
        if (!$object) {
681
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
682
        }
683
684
        $this->admin->checkAccess('history', $object);
685
686
        $manager = $this->get('sonata.admin.audit.manager');
687
688
        if (!$manager->hasReader($this->admin->getClass())) {
689
            throw $this->createNotFoundException(
690
                sprintf(
691
                    'unable to find the audit reader for class : %s',
692
                    $this->admin->getClass()
693
                )
694
            );
695
        }
696
697
        $reader = $manager->getReader($this->admin->getClass());
698
699
        $revisions = $reader->findRevisions($this->admin->getClass(), $id);
700
701
        return $this->renderWithExtraParams($this->admin->getTemplate('history'), [
702
            'action' => 'history',
703
            'object' => $object,
704
            'revisions' => $revisions,
705
            'currentRevision' => $revisions ? current($revisions) : false,
706
        ], null);
707
    }
708
709
    /**
710
     * View history revision of object.
711
     *
712
     * @param int|string|null $id
713
     * @param string|null     $revision
714
     *
715
     * @return Response
716
     *
717
     * @throws AccessDeniedException If access is not granted
718
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
719
     */
720
    public function historyViewRevisionAction($id = null, $revision = null)
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...
721
    {
722
        $request = $this->getRequest();
723
        $id = $request->get($this->admin->getIdParameter());
724
725
        $object = $this->admin->getObject($id);
726
727
        if (!$object) {
728
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
729
        }
730
731
        $this->admin->checkAccess('historyViewRevision', $object);
732
733
        $manager = $this->get('sonata.admin.audit.manager');
734
735
        if (!$manager->hasReader($this->admin->getClass())) {
736
            throw $this->createNotFoundException(
737
                sprintf(
738
                    'unable to find the audit reader for class : %s',
739
                    $this->admin->getClass()
740
                )
741
            );
742
        }
743
744
        $reader = $manager->getReader($this->admin->getClass());
745
746
        // retrieve the revisioned object
747
        $object = $reader->find($this->admin->getClass(), $id, $revision);
748
749
        if (!$object) {
750
            throw $this->createNotFoundException(
751
                sprintf(
752
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
753
                    $id,
754
                    $revision,
755
                    $this->admin->getClass()
756
                )
757
            );
758
        }
759
760
        $this->admin->setSubject($object);
761
762
        return $this->renderWithExtraParams($this->admin->getTemplate('show'), [
763
            'action' => 'show',
764
            'object' => $object,
765
            'elements' => $this->admin->getShow(),
766
        ], null);
767
    }
768
769
    /**
770
     * Compare history revisions of object.
771
     *
772
     * @param int|string|null $id
773
     * @param int|string|null $base_revision
774
     * @param int|string|null $compare_revision
775
     *
776
     * @return Response
777
     *
778
     * @throws AccessDeniedException If access is not granted
779
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
780
     */
781
    public function historyCompareRevisionsAction($id = null, $base_revision = null, $compare_revision = null)
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...
782
    {
783
        $request = $this->getRequest();
784
785
        $this->admin->checkAccess('historyCompareRevisions');
786
787
        $id = $request->get($this->admin->getIdParameter());
788
789
        $object = $this->admin->getObject($id);
790
791
        if (!$object) {
792
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
793
        }
794
795
        $manager = $this->get('sonata.admin.audit.manager');
796
797
        if (!$manager->hasReader($this->admin->getClass())) {
798
            throw $this->createNotFoundException(
799
                sprintf(
800
                    'unable to find the audit reader for class : %s',
801
                    $this->admin->getClass()
802
                )
803
            );
804
        }
805
806
        $reader = $manager->getReader($this->admin->getClass());
807
808
        // retrieve the base revision
809
        $base_object = $reader->find($this->admin->getClass(), $id, $base_revision);
810
        if (!$base_object) {
811
            throw $this->createNotFoundException(
812
                sprintf(
813
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
814
                    $id,
815
                    $base_revision,
816
                    $this->admin->getClass()
817
                )
818
            );
819
        }
820
821
        // retrieve the compare revision
822
        $compare_object = $reader->find($this->admin->getClass(), $id, $compare_revision);
823
        if (!$compare_object) {
824
            throw $this->createNotFoundException(
825
                sprintf(
826
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
827
                    $id,
828
                    $compare_revision,
829
                    $this->admin->getClass()
830
                )
831
            );
832
        }
833
834
        $this->admin->setSubject($base_object);
835
836
        return $this->renderWithExtraParams($this->admin->getTemplate('show_compare'), [
837
            'action' => 'show',
838
            'object' => $base_object,
839
            'object_compare' => $compare_object,
840
            'elements' => $this->admin->getShow(),
841
        ], null);
842
    }
843
844
    /**
845
     * Export data to specified format.
846
     *
847
     * @param Request $request
848
     *
849
     * @return Response
850
     *
851
     * @throws AccessDeniedException If access is not granted
852
     * @throws \RuntimeException     If the export format is invalid
853
     */
854
    public function exportAction(Request $request)
855
    {
856
        $this->admin->checkAccess('export');
857
858
        $format = $request->get('format');
859
860
        // NEXT_MAJOR: remove the check
861
        if (!$this->has('sonata.admin.admin_exporter')) {
862
            @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...
863
                'Not registering the exporter bundle is deprecated since version 3.14.'
864
                .' You must register it to be able to use the export action in 4.0.',
865
                E_USER_DEPRECATED
866
            );
867
            $allowedExportFormats = (array) $this->admin->getExportFormats();
868
869
            $class = $this->admin->getClass();
870
            $filename = sprintf(
871
                'export_%s_%s.%s',
872
                strtolower(substr($class, strripos($class, '\\') + 1)),
873
                date('Y_m_d_H_i_s', strtotime('now')),
874
                $format
875
            );
876
            $exporter = $this->get('sonata.admin.exporter');
877
        } else {
878
            $adminExporter = $this->get('sonata.admin.admin_exporter');
879
            $allowedExportFormats = $adminExporter->getAvailableFormats($this->admin);
880
            $filename = $adminExporter->getExportFilename($this->admin, $format);
881
            $exporter = $this->get('sonata.exporter.exporter');
882
        }
883
884
        if (!in_array($format, $allowedExportFormats)) {
885
            throw new \RuntimeException(
886
                sprintf(
887
                    'Export in format `%s` is not allowed for class: `%s`. Allowed formats are: `%s`',
888
                    $format,
889
                    $this->admin->getClass(),
890
                    implode(', ', $allowedExportFormats)
891
                )
892
            );
893
        }
894
895
        return $exporter->getResponse(
896
            $format,
897
            $filename,
898
            $this->admin->getDataSourceIterator()
899
        );
900
    }
901
902
    /**
903
     * Returns the Response object associated to the acl action.
904
     *
905
     * @param int|string|null $id
906
     *
907
     * @return Response|RedirectResponse
908
     *
909
     * @throws AccessDeniedException If access is not granted
910
     * @throws NotFoundHttpException If the object does not exist or the ACL is not enabled
911
     */
912
    public function aclAction($id = null)
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...
913
    {
914
        $request = $this->getRequest();
915
916
        if (!$this->admin->isAclEnabled()) {
917
            throw $this->createNotFoundException('ACL are not enabled for this admin');
918
        }
919
920
        $id = $request->get($this->admin->getIdParameter());
921
922
        $object = $this->admin->getObject($id);
923
924
        if (!$object) {
925
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
926
        }
927
928
        $this->admin->checkAccess('acl', $object);
929
930
        $this->admin->setSubject($object);
931
        $aclUsers = $this->getAclUsers();
932
        $aclRoles = $this->getAclRoles();
933
934
        $adminObjectAclManipulator = $this->get('sonata.admin.object.manipulator.acl.admin');
935
        $adminObjectAclData = new AdminObjectAclData(
936
            $this->admin,
937
            $object,
938
            $aclUsers,
939
            $adminObjectAclManipulator->getMaskBuilderClass(),
940
            $aclRoles
941
        );
942
943
        $aclUsersForm = $adminObjectAclManipulator->createAclUsersForm($adminObjectAclData);
944
        $aclRolesForm = $adminObjectAclManipulator->createAclRolesForm($adminObjectAclData);
945
946
        if ('POST' === $request->getMethod()) {
947
            if ($request->request->has(AdminObjectAclManipulator::ACL_USERS_FORM_NAME)) {
948
                $form = $aclUsersForm;
949
                $updateMethod = 'updateAclUsers';
950
            } elseif ($request->request->has(AdminObjectAclManipulator::ACL_ROLES_FORM_NAME)) {
951
                $form = $aclRolesForm;
952
                $updateMethod = 'updateAclRoles';
953
            }
954
955
            if (isset($form)) {
956
                $form->handleRequest($request);
957
958
                if ($form->isValid()) {
959
                    $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...
960
                    $this->addFlash(
961
                        'sonata_flash_success',
962
                        $this->trans('flash_acl_edit_success', [], 'SonataAdminBundle')
963
                    );
964
965
                    return new RedirectResponse($this->admin->generateObjectUrl('acl', $object));
966
                }
967
            }
968
        }
969
970
        return $this->renderWithExtraParams($this->admin->getTemplate('acl'), [
971
            'action' => 'acl',
972
            'permissions' => $adminObjectAclData->getUserPermissions(),
973
            'object' => $object,
974
            'users' => $aclUsers,
975
            'roles' => $aclRoles,
976
            'aclUsersForm' => $aclUsersForm->createView(),
977
            'aclRolesForm' => $aclRolesForm->createView(),
978
        ], null);
979
    }
980
981
    /**
982
     * @return Request
983
     */
984
    public function getRequest()
985
    {
986
        if ($this->container->has('request_stack')) {
987
            return $this->container->get('request_stack')->getCurrentRequest();
988
        }
989
990
        return $this->container->get('request');
991
    }
992
993
    /**
994
     * Render JSON.
995
     *
996
     * @param mixed $data
997
     * @param int   $status
998
     * @param array $headers
999
     *
1000
     * @return Response with json encoded data
1001
     */
1002
    protected function renderJson($data, $status = 200, $headers = [])
1003
    {
1004
        return new JsonResponse($data, $status, $headers);
1005
    }
1006
1007
    /**
1008
     * Returns true if the request is a XMLHttpRequest.
1009
     *
1010
     * @return bool True if the request is an XMLHttpRequest, false otherwise
1011
     */
1012
    protected function isXmlHttpRequest()
1013
    {
1014
        $request = $this->getRequest();
1015
1016
        return $request->isXmlHttpRequest() || $request->get('_xml_http_request');
1017
    }
1018
1019
    /**
1020
     * Returns the correct RESTful verb, given either by the request itself or
1021
     * via the "_method" parameter.
1022
     *
1023
     * @return string HTTP method, either
1024
     */
1025
    protected function getRestMethod()
1026
    {
1027
        $request = $this->getRequest();
1028
1029
        if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) {
1030
            return $request->getMethod();
1031
        }
1032
1033
        return $request->request->get('_method');
1034
    }
1035
1036
    /**
1037
     * Contextualize the admin class depends on the current request.
1038
     *
1039
     * @throws \RuntimeException
1040
     */
1041
    protected function configure()
1042
    {
1043
        $request = $this->getRequest();
1044
1045
        $adminCode = $request->get('_sonata_admin');
1046
1047
        if (!$adminCode) {
1048
            throw new \RuntimeException(sprintf(
1049
                'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`',
1050
                get_class($this),
1051
                $request->get('_route')
1052
            ));
1053
        }
1054
1055
        $this->admin = $this->container->get('sonata.admin.pool')->getAdminByAdminCode($adminCode);
1056
1057
        if (!$this->admin) {
1058
            throw new \RuntimeException(sprintf(
1059
                'Unable to find the admin class related to the current controller (%s)',
1060
                get_class($this)
1061
            ));
1062
        }
1063
1064
        $rootAdmin = $this->admin;
1065
1066
        while ($rootAdmin->isChild()) {
1067
            $rootAdmin->setCurrentChild(true);
1068
            $rootAdmin = $rootAdmin->getParent();
1069
        }
1070
1071
        $rootAdmin->setRequest($request);
1072
1073
        if ($request->get('uniqid')) {
1074
            $this->admin->setUniqid($request->get('uniqid'));
1075
        }
1076
    }
1077
1078
    /**
1079
     * Proxy for the logger service of the container.
1080
     * If no such service is found, a NullLogger is returned.
1081
     *
1082
     * @return LoggerInterface
1083
     */
1084
    protected function getLogger()
1085
    {
1086
        if ($this->container->has('logger')) {
1087
            return $this->container->get('logger');
1088
        }
1089
1090
        return new NullLogger();
1091
    }
1092
1093
    /**
1094
     * Returns the base template name.
1095
     *
1096
     * @return string The template name
1097
     */
1098
    protected function getBaseTemplate()
1099
    {
1100
        if ($this->isXmlHttpRequest()) {
1101
            return $this->admin->getTemplate('ajax');
1102
        }
1103
1104
        return $this->admin->getTemplate('layout');
1105
    }
1106
1107
    /**
1108
     * @param \Exception $e
1109
     *
1110
     * @throws \Exception
1111
     */
1112
    protected function handleModelManagerException(\Exception $e)
1113
    {
1114
        if ($this->get('kernel')->isDebug()) {
1115
            throw $e;
1116
        }
1117
1118
        $context = ['exception' => $e];
1119
        if ($e->getPrevious()) {
1120
            $context['previous_exception_message'] = $e->getPrevious()->getMessage();
1121
        }
1122
        $this->getLogger()->error($e->getMessage(), $context);
1123
    }
1124
1125
    /**
1126
     * Redirect the user depend on this choice.
1127
     *
1128
     * @param object $object
1129
     *
1130
     * @return RedirectResponse
1131
     */
1132
    protected function redirectTo($object)
1133
    {
1134
        $request = $this->getRequest();
1135
1136
        $url = false;
1137
1138
        if (null !== $request->get('btn_update_and_list')) {
1139
            $url = $this->admin->generateUrl('list');
1140
        }
1141
        if (null !== $request->get('btn_create_and_list')) {
1142
            $url = $this->admin->generateUrl('list');
1143
        }
1144
1145
        if (null !== $request->get('btn_create_and_create')) {
1146
            $params = [];
1147
            if ($this->admin->hasActiveSubClass()) {
1148
                $params['subclass'] = $request->get('subclass');
1149
            }
1150
            $url = $this->admin->generateUrl('create', $params);
1151
        }
1152
1153
        if ('DELETE' === $this->getRestMethod()) {
1154
            $url = $this->admin->generateUrl('list');
1155
        }
1156
1157
        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...
1158
            foreach (['edit', 'show'] as $route) {
1159
                if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route, $object)) {
1160
                    $url = $this->admin->generateObjectUrl($route, $object);
1161
1162
                    break;
1163
                }
1164
            }
1165
        }
1166
1167
        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...
1168
            $url = $this->admin->generateUrl('list');
1169
        }
1170
1171
        return new RedirectResponse($url);
1172
    }
1173
1174
    /**
1175
     * Returns true if the preview is requested to be shown.
1176
     *
1177
     * @return bool
1178
     */
1179
    protected function isPreviewRequested()
1180
    {
1181
        $request = $this->getRequest();
1182
1183
        return null !== $request->get('btn_preview');
1184
    }
1185
1186
    /**
1187
     * Returns true if the preview has been approved.
1188
     *
1189
     * @return bool
1190
     */
1191
    protected function isPreviewApproved()
1192
    {
1193
        $request = $this->getRequest();
1194
1195
        return null !== $request->get('btn_preview_approve');
1196
    }
1197
1198
    /**
1199
     * Returns true if the request is in the preview workflow.
1200
     *
1201
     * That means either a preview is requested or the preview has already been shown
1202
     * and it got approved/declined.
1203
     *
1204
     * @return bool
1205
     */
1206
    protected function isInPreviewMode()
1207
    {
1208
        return $this->admin->supportsPreviewMode()
1209
        && ($this->isPreviewRequested()
1210
            || $this->isPreviewApproved()
1211
            || $this->isPreviewDeclined());
1212
    }
1213
1214
    /**
1215
     * Returns true if the preview has been declined.
1216
     *
1217
     * @return bool
1218
     */
1219
    protected function isPreviewDeclined()
1220
    {
1221
        $request = $this->getRequest();
1222
1223
        return null !== $request->get('btn_preview_decline');
1224
    }
1225
1226
    /**
1227
     * Gets ACL users.
1228
     *
1229
     * @return \Traversable
1230
     */
1231
    protected function getAclUsers()
1232
    {
1233
        $aclUsers = [];
1234
1235
        $userManagerServiceName = $this->container->getParameter('sonata.admin.security.acl_user_manager');
1236
        if (null !== $userManagerServiceName && $this->has($userManagerServiceName)) {
1237
            $userManager = $this->get($userManagerServiceName);
1238
1239
            if (method_exists($userManager, 'findUsers')) {
1240
                $aclUsers = $userManager->findUsers();
1241
            }
1242
        }
1243
1244
        return is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
1245
    }
1246
1247
    /**
1248
     * Gets ACL roles.
1249
     *
1250
     * @return \Traversable
1251
     */
1252
    protected function getAclRoles()
1253
    {
1254
        $aclRoles = [];
1255
        $roleHierarchy = $this->container->getParameter('security.role_hierarchy.roles');
1256
        $pool = $this->container->get('sonata.admin.pool');
1257
1258
        foreach ($pool->getAdminServiceIds() as $id) {
1259
            try {
1260
                $admin = $pool->getInstance($id);
1261
            } catch (\Exception $e) {
1262
                continue;
1263
            }
1264
1265
            $baseRole = $admin->getSecurityHandler()->getBaseRole($admin);
1266
            foreach ($admin->getSecurityInformation() as $role => $permissions) {
1267
                $role = sprintf($baseRole, $role);
1268
                $aclRoles[] = $role;
1269
            }
1270
        }
1271
1272
        foreach ($roleHierarchy as $name => $roles) {
1273
            $aclRoles[] = $name;
1274
            $aclRoles = array_merge($aclRoles, $roles);
1275
        }
1276
1277
        $aclRoles = array_unique($aclRoles);
1278
1279
        return is_array($aclRoles) ? new \ArrayIterator($aclRoles) : $aclRoles;
1280
    }
1281
1282
    /**
1283
     * Validate CSRF token for action without form.
1284
     *
1285
     * @param string $intention
1286
     *
1287
     * @throws HttpException
1288
     */
1289
    protected function validateCsrfToken($intention)
1290
    {
1291
        $request = $this->getRequest();
1292
        $token = $request->request->get('_sonata_csrf_token', false);
1293
1294
        if ($this->container->has('security.csrf.token_manager')) { // SF3.0
1295
            $valid = $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($intention, $token));
1296
        } elseif ($this->container->has('form.csrf_provider')) { // < SF3.0
1297
            $valid = $this->container->get('form.csrf_provider')->isCsrfTokenValid($intention, $token);
1298
        } else {
1299
            return;
1300
        }
1301
1302
        if (!$valid) {
1303
            throw new HttpException(400, 'The csrf token is not valid, CSRF attack?');
1304
        }
1305
    }
1306
1307
    /**
1308
     * Escape string for html output.
1309
     *
1310
     * @param string $s
1311
     *
1312
     * @return string
1313
     */
1314
    protected function escapeHtml($s)
1315
    {
1316
        return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1317
    }
1318
1319
    /**
1320
     * Get CSRF token.
1321
     *
1322
     * @param string $intention
1323
     *
1324
     * @return string|false
1325
     */
1326
    protected function getCsrfToken($intention)
1327
    {
1328
        if ($this->container->has('security.csrf.token_manager')) {
1329
            return $this->container->get('security.csrf.token_manager')->getToken($intention)->getValue();
1330
        }
1331
1332
        // TODO: Remove it when bumping requirements to SF 2.4+
1333
        if ($this->container->has('form.csrf_provider')) {
1334
            return $this->container->get('form.csrf_provider')->generateCsrfToken($intention);
1335
        }
1336
1337
        return false;
1338
    }
1339
1340
    /**
1341
     * This method can be overloaded in your custom CRUD controller.
1342
     * It's called from createAction.
1343
     *
1344
     * @param Request $request
1345
     * @param mixed   $object
1346
     *
1347
     * @return Response|null
1348
     */
1349
    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...
1350
    {
1351
    }
1352
1353
    /**
1354
     * This method can be overloaded in your custom CRUD controller.
1355
     * It's called from editAction.
1356
     *
1357
     * @param Request $request
1358
     * @param mixed   $object
1359
     *
1360
     * @return Response|null
1361
     */
1362
    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...
1363
    {
1364
    }
1365
1366
    /**
1367
     * This method can be overloaded in your custom CRUD controller.
1368
     * It's called from deleteAction.
1369
     *
1370
     * @param Request $request
1371
     * @param mixed   $object
1372
     *
1373
     * @return Response|null
1374
     */
1375
    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...
1376
    {
1377
    }
1378
1379
    /**
1380
     * This method can be overloaded in your custom CRUD controller.
1381
     * It's called from showAction.
1382
     *
1383
     * @param Request $request
1384
     * @param mixed   $object
1385
     *
1386
     * @return Response|null
1387
     */
1388
    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...
1389
    {
1390
    }
1391
1392
    /**
1393
     * This method can be overloaded in your custom CRUD controller.
1394
     * It's called from listAction.
1395
     *
1396
     * @param Request $request
1397
     *
1398
     * @return Response|null
1399
     */
1400
    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...
1401
    {
1402
    }
1403
1404
    /**
1405
     * Translate a message id.
1406
     *
1407
     * @param string $id
1408
     * @param array  $parameters
1409
     * @param string $domain
1410
     * @param string $locale
1411
     *
1412
     * @return string translated string
1413
     */
1414
    final protected function trans($id, array $parameters = [], $domain = null, $locale = null)
1415
    {
1416
        $domain = $domain ?: $this->admin->getTranslationDomain();
1417
1418
        return $this->get('translator')->trans($id, $parameters, $domain, $locale);
1419
    }
1420
1421
    /**
1422
     * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
1423
     *
1424
     * @param FormView $formView
1425
     * @param string   $theme
1426
     */
1427
    private function setFormTheme(FormView $formView, $theme)
1428
    {
1429
        $twig = $this->get('twig');
1430
1431
        // BC for Symfony < 3.2 where this runtime does not exists
1432
        if (!method_exists(AppVariable::class, 'getToken')) {
1433
            $twig->getExtension(FormExtension::class)->renderer->setTheme($formView, $theme);
1434
1435
            return;
1436
        }
1437
1438
        // BC for Symfony < 3.4 where runtime should be TwigRenderer
1439
        if (!method_exists(DebugCommand::class, 'getLoaderPaths')) {
1440
            $twig->getRuntime(TwigRenderer::class)->setTheme($formView, $theme);
1441
1442
            return;
1443
        }
1444
1445
        $twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme);
1446
    }
1447
}
1448