Completed
Push — 3.x ( 6b78b0...2579fc )
by Grégoire
04:21
created

AbstractAdmin::createQuery()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 4
nop 1
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\Admin;
13
14
use Doctrine\Common\Util\ClassUtils;
15
use Knp\Menu\FactoryInterface as MenuFactoryInterface;
16
use Knp\Menu\ItemInterface;
17
use Knp\Menu\ItemInterface as MenuItemInterface;
18
use Sonata\AdminBundle\Builder\DatagridBuilderInterface;
19
use Sonata\AdminBundle\Builder\FormContractorInterface;
20
use Sonata\AdminBundle\Builder\ListBuilderInterface;
21
use Sonata\AdminBundle\Builder\RouteBuilderInterface;
22
use Sonata\AdminBundle\Builder\ShowBuilderInterface;
23
use Sonata\AdminBundle\Datagrid\DatagridInterface;
24
use Sonata\AdminBundle\Datagrid\DatagridMapper;
25
use Sonata\AdminBundle\Datagrid\ListMapper;
26
use Sonata\AdminBundle\Datagrid\Pager;
27
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
28
use Sonata\AdminBundle\Filter\Persister\FilterPersisterInterface;
29
use Sonata\AdminBundle\Form\FormMapper;
30
use Sonata\AdminBundle\Form\Type\ModelHiddenType;
31
use Sonata\AdminBundle\Model\ModelManagerInterface;
32
use Sonata\AdminBundle\Route\RouteCollection;
33
use Sonata\AdminBundle\Route\RouteGeneratorInterface;
34
use Sonata\AdminBundle\Security\Handler\AclSecurityHandlerInterface;
35
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
36
use Sonata\AdminBundle\Show\ShowMapper;
37
use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface;
38
use Sonata\CoreBundle\Model\Metadata;
39
use Sonata\CoreBundle\Validator\Constraints\InlineConstraint;
40
use Sonata\CoreBundle\Validator\ErrorElement;
41
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
42
use Symfony\Component\Form\Form;
43
use Symfony\Component\Form\FormBuilderInterface;
44
use Symfony\Component\Form\FormEvent;
45
use Symfony\Component\Form\FormEvents;
46
use Symfony\Component\HttpFoundation\Request;
47
use Symfony\Component\PropertyAccess\PropertyPath;
48
use Symfony\Component\Routing\Generator\UrlGeneratorInterface as RoutingUrlGeneratorInterface;
49
use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
50
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
51
use Symfony\Component\Translation\TranslatorInterface;
52
use Symfony\Component\Validator\Validator\ValidatorInterface;
53
54
/**
55
 * @author Thomas Rabaix <[email protected]>
56
 */
57
abstract class AbstractAdmin implements AdminInterface, DomainObjectInterface, AdminTreeInterface
58
{
59
    const CONTEXT_MENU = 'menu';
60
    const CONTEXT_DASHBOARD = 'dashboard';
61
62
    const CLASS_REGEX =
63
        '@
64
        (?:([A-Za-z0-9]*)\\\)?        # vendor name / app name
65
        (Bundle\\\)?                  # optional bundle directory
66
        ([A-Za-z0-9]+?)(?:Bundle)?\\\ # bundle name, with optional suffix
67
        (
68
            Entity|Document|Model|PHPCR|CouchDocument|Phpcr|
69
            Doctrine\\\Orm|Doctrine\\\Phpcr|Doctrine\\\MongoDB|Doctrine\\\CouchDB
70
        )\\\(.*)@x';
71
72
    const MOSAIC_ICON_CLASS = 'fa fa-th-large fa-fw';
73
74
    /**
75
     * The list FieldDescription constructed from the configureListField method.
76
     *
77
     * @var array
78
     */
79
    protected $listFieldDescriptions = [];
80
81
    /**
82
     * The show FieldDescription constructed from the configureShowFields method.
83
     *
84
     * @var array
85
     */
86
    protected $showFieldDescriptions = [];
87
88
    /**
89
     * The list FieldDescription constructed from the configureFormField method.
90
     *
91
     * @var array
92
     */
93
    protected $formFieldDescriptions = [];
94
95
    /**
96
     * The filter FieldDescription constructed from the configureFilterField method.
97
     *
98
     * @var array
99
     */
100
    protected $filterFieldDescriptions = [];
101
102
    /**
103
     * The number of result to display in the list.
104
     *
105
     * @var int
106
     */
107
    protected $maxPerPage = 32;
108
109
    /**
110
     * The maximum number of page numbers to display in the list.
111
     *
112
     * @var int
113
     */
114
    protected $maxPageLinks = 25;
115
116
    /**
117
     * The base route name used to generate the routing information.
118
     *
119
     * @var string
120
     */
121
    protected $baseRouteName;
122
123
    /**
124
     * The base route pattern used to generate the routing information.
125
     *
126
     * @var string
127
     */
128
    protected $baseRoutePattern;
129
130
    /**
131
     * The base name controller used to generate the routing information.
132
     *
133
     * @var string
134
     */
135
    protected $baseControllerName;
136
137
    /**
138
     * The label class name  (used in the title/breadcrumb ...).
139
     *
140
     * @var string
141
     */
142
    protected $classnameLabel;
143
144
    /**
145
     * The translation domain to be used to translate messages.
146
     *
147
     * @var string
148
     */
149
    protected $translationDomain = 'messages';
150
151
    /**
152
     * Options to set to the form (ie, validation_groups).
153
     *
154
     * @var array
155
     */
156
    protected $formOptions = [];
157
158
    /**
159
     * Default values to the datagrid.
160
     *
161
     * @var array
162
     */
163
    protected $datagridValues = [
164
        '_page' => 1,
165
        '_per_page' => 32,
166
    ];
167
168
    /**
169
     * Predefined per page options.
170
     *
171
     * @var array
172
     */
173
    protected $perPageOptions = [16, 32, 64, 128, 192];
174
175
    /**
176
     * Pager type.
177
     *
178
     * @var string
179
     */
180
    protected $pagerType = Pager::TYPE_DEFAULT;
181
182
    /**
183
     * The code related to the admin.
184
     *
185
     * @var string
186
     */
187
    protected $code;
188
189
    /**
190
     * The label.
191
     *
192
     * @var string
193
     */
194
    protected $label;
195
196
    /**
197
     * Whether or not to persist the filters in the session.
198
     *
199
     * NEXT_MAJOR: remove this property
200
     *
201
     * @var bool
202
     *
203
     * @deprecated since 3.x, to be removed in 4.0.
204
     */
205
    protected $persistFilters = false;
206
207
    /**
208
     * Array of routes related to this admin.
209
     *
210
     * @var RouteCollection
211
     */
212
    protected $routes;
213
214
    /**
215
     * The subject only set in edit/update/create mode.
216
     *
217
     * @var object
218
     */
219
    protected $subject;
220
221
    /**
222
     * Define a Collection of child admin, ie /admin/order/{id}/order-element/{childId}.
223
     *
224
     * @var array
225
     */
226
    protected $children = [];
227
228
    /**
229
     * Reference the parent collection.
230
     *
231
     * @var AdminInterface|null
232
     */
233
    protected $parent = null;
234
235
    /**
236
     * The base code route refer to the prefix used to generate the route name.
237
     *
238
     * NEXT_MAJOR: remove this attribute.
239
     *
240
     * @deprecated This attribute is deprecated since 3.24 and will be removed in 4.0
241
     *
242
     * @var string
243
     */
244
    protected $baseCodeRoute = '';
245
246
    /**
247
     * The related parent association, ie if OrderElement has a parent property named order,
248
     * then the $parentAssociationMapping must be a string named `order`.
249
     *
250
     * @var string
251
     */
252
    protected $parentAssociationMapping = null;
253
254
    /**
255
     * Reference the parent FieldDescription related to this admin
256
     * only set for FieldDescription which is associated to an Sub Admin instance.
257
     *
258
     * @var FieldDescriptionInterface
259
     */
260
    protected $parentFieldDescription;
261
262
    /**
263
     * If true then the current admin is part of the nested admin set (from the url).
264
     *
265
     * @var bool
266
     */
267
    protected $currentChild = false;
268
269
    /**
270
     * The uniqid is used to avoid clashing with 2 admin related to the code
271
     * ie: a Block linked to a Block.
272
     *
273
     * @var string
274
     */
275
    protected $uniqid;
276
277
    /**
278
     * The Entity or Document manager.
279
     *
280
     * @var ModelManagerInterface
281
     */
282
    protected $modelManager;
283
284
    /**
285
     * The current request object.
286
     *
287
     * @var \Symfony\Component\HttpFoundation\Request
288
     */
289
    protected $request;
290
291
    /**
292
     * The translator component.
293
     *
294
     * NEXT_MAJOR: remove this property
295
     *
296
     * @var \Symfony\Component\Translation\TranslatorInterface
297
     *
298
     * @deprecated since 3.9, to be removed with 4.0
299
     */
300
    protected $translator;
301
302
    /**
303
     * The related form contractor.
304
     *
305
     * @var FormContractorInterface
306
     */
307
    protected $formContractor;
308
309
    /**
310
     * The related list builder.
311
     *
312
     * @var ListBuilderInterface
313
     */
314
    protected $listBuilder;
315
316
    /**
317
     * The related view builder.
318
     *
319
     * @var ShowBuilderInterface
320
     */
321
    protected $showBuilder;
322
323
    /**
324
     * The related datagrid builder.
325
     *
326
     * @var DatagridBuilderInterface
327
     */
328
    protected $datagridBuilder;
329
330
    /**
331
     * @var RouteBuilderInterface
332
     */
333
    protected $routeBuilder;
334
335
    /**
336
     * The datagrid instance.
337
     *
338
     * @var \Sonata\AdminBundle\Datagrid\DatagridInterface
339
     */
340
    protected $datagrid;
341
342
    /**
343
     * The router instance.
344
     *
345
     * @var RouteGeneratorInterface
346
     */
347
    protected $routeGenerator;
348
349
    /**
350
     * The generated breadcrumbs.
351
     *
352
     * NEXT_MAJOR : remove this property
353
     *
354
     * @var array
355
     */
356
    protected $breadcrumbs = [];
357
358
    /**
359
     * @var SecurityHandlerInterface
360
     */
361
    protected $securityHandler = null;
362
363
    /**
364
     * @var ValidatorInterface
365
     */
366
    protected $validator = null;
367
368
    /**
369
     * The configuration pool.
370
     *
371
     * @var Pool
372
     */
373
    protected $configurationPool;
374
375
    /**
376
     * @var MenuItemInterface
377
     */
378
    protected $menu;
379
380
    /**
381
     * @var MenuFactoryInterface
382
     */
383
    protected $menuFactory;
384
385
    /**
386
     * @var array
387
     */
388
    protected $loaded = [
389
        'view_fields' => false,
390
        'view_groups' => false,
391
        'routes' => false,
392
        'tab_menu' => false,
393
    ];
394
395
    /**
396
     * @var array
397
     */
398
    protected $formTheme = [];
399
400
    /**
401
     * @var array
402
     */
403
    protected $filterTheme = [];
404
405
    /**
406
     * @var array
407
     */
408
    protected $templates = [];
409
410
    /**
411
     * @var AdminExtensionInterface[]
412
     */
413
    protected $extensions = [];
414
415
    /**
416
     * @var LabelTranslatorStrategyInterface
417
     */
418
    protected $labelTranslatorStrategy;
419
420
    /**
421
     * Setting to true will enable preview mode for
422
     * the entity and show a preview button in the
423
     * edit/create forms.
424
     *
425
     * @var bool
426
     */
427
    protected $supportsPreviewMode = false;
428
429
    /**
430
     * Roles and permissions per role.
431
     *
432
     * @var array 'role' => ['permission', 'permission']
433
     */
434
    protected $securityInformation = [];
435
436
    protected $cacheIsGranted = [];
437
438
    /**
439
     * Action list for the search result.
440
     *
441
     * @var string[]
442
     */
443
    protected $searchResultActions = ['edit', 'show'];
444
445
    protected $listModes = [
446
        'list' => [
447
            'class' => 'fa fa-list fa-fw',
448
        ],
449
        'mosaic' => [
450
            'class' => self::MOSAIC_ICON_CLASS,
451
        ],
452
    ];
453
454
    /**
455
     * The Access mapping.
456
     *
457
     * @var array [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
458
     */
459
    protected $accessMapping = [];
460
461
    /**
462
     * The class name managed by the admin class.
463
     *
464
     * @var string
465
     */
466
    private $class;
467
468
    /**
469
     * The subclasses supported by the admin class.
470
     *
471
     * @var array
472
     */
473
    private $subClasses = [];
474
475
    /**
476
     * The list collection.
477
     *
478
     * @var array
479
     */
480
    private $list;
481
482
    /**
483
     * @var FieldDescriptionCollection
484
     */
485
    private $show;
486
487
    /**
488
     * @var Form
489
     */
490
    private $form;
491
492
    /**
493
     * @var DatagridInterface
494
     */
495
    private $filter;
0 ignored issues
show
Unused Code introduced by
The property $filter is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
496
497
    /**
498
     * The cached base route name.
499
     *
500
     * @var string
501
     */
502
    private $cachedBaseRouteName;
503
504
    /**
505
     * The cached base route pattern.
506
     *
507
     * @var string
508
     */
509
    private $cachedBaseRoutePattern;
510
511
    /**
512
     * The form group disposition.
513
     *
514
     * @var array|bool
515
     */
516
    private $formGroups = false;
517
518
    /**
519
     * The form tabs disposition.
520
     *
521
     * @var array|bool
522
     */
523
    private $formTabs = false;
524
525
    /**
526
     * The view group disposition.
527
     *
528
     * @var array|bool
529
     */
530
    private $showGroups = false;
531
532
    /**
533
     * The view tab disposition.
534
     *
535
     * @var array|bool
536
     */
537
    private $showTabs = false;
538
539
    /**
540
     * The manager type to use for the admin.
541
     *
542
     * @var string
543
     */
544
    private $managerType;
545
546
    /**
547
     * The breadcrumbsBuilder component.
548
     *
549
     * @var BreadcrumbsBuilderInterface
550
     */
551
    private $breadcrumbsBuilder;
552
553
    /**
554
     * Component responsible for persisting filters.
555
     *
556
     * @var FilterPersisterInterface|null
557
     */
558
    private $filterPersister;
559
560
    /**
561
     * @param string $code
562
     * @param string $class
563
     * @param string $baseControllerName
564
     */
565
    public function __construct($code, $class, $baseControllerName)
566
    {
567
        $this->code = $code;
568
        $this->class = $class;
569
        $this->baseControllerName = $baseControllerName;
570
571
        $this->predefinePerPageOptions();
572
        $this->datagridValues['_per_page'] = $this->maxPerPage;
573
    }
574
575
    /**
576
     * {@inheritdoc}
577
     *
578
     * NEXT_MAJOR: return null to indicate no override
579
     */
580
    public function getExportFormats()
581
    {
582
        return [
583
            'json', 'xml', 'csv', 'xls',
584
        ];
585
    }
586
587
    /**
588
     * @return array
589
     */
590
    public function getExportFields()
591
    {
592
        $fields = $this->getModelManager()->getExportFields($this->getClass());
593
594
        foreach ($this->getExtensions() as $extension) {
595
            if (method_exists($extension, 'configureExportFields')) {
596
                $fields = $extension->configureExportFields($this, $fields);
597
            }
598
        }
599
600
        return $fields;
601
    }
602
603
    public function getDataSourceIterator()
604
    {
605
        $datagrid = $this->getDatagrid();
606
        $datagrid->buildPager();
607
608
        $fields = [];
609
610
        foreach ($this->getExportFields() as $key => $field) {
611
            $label = $this->getTranslationLabel($field, 'export', 'label');
612
            $transLabel = $this->trans($label);
613
614
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
615
            // No translation key exists
616
            if ($transLabel == $label) {
617
                $fields[$key] = $field;
618
            } else {
619
                $fields[$transLabel] = $field;
620
            }
621
        }
622
623
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
624
    }
625
626
    public function validate(ErrorElement $errorElement, $object)
627
    {
628
    }
629
630
    /**
631
     * define custom variable.
632
     */
633
    public function initialize()
634
    {
635
        if (!$this->classnameLabel) {
636
            $this->classnameLabel = substr($this->getClass(), strrpos($this->getClass(), '\\') + 1);
637
        }
638
639
        // NEXT_MAJOR: Remove this line.
640
        $this->baseCodeRoute = $this->getCode();
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since 3.24 and will be removed in 4.0

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

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

Loading history...
641
642
        $this->configure();
643
    }
644
645
    public function configure()
646
    {
647
    }
648
649
    public function update($object)
650
    {
651
        $this->preUpdate($object);
652
        foreach ($this->extensions as $extension) {
653
            $extension->preUpdate($this, $object);
654
        }
655
656
        $result = $this->getModelManager()->update($object);
657
        // BC compatibility
658
        if (null !== $result) {
659
            $object = $result;
660
        }
661
662
        $this->postUpdate($object);
663
        foreach ($this->extensions as $extension) {
664
            $extension->postUpdate($this, $object);
665
        }
666
667
        return $object;
668
    }
669
670
    public function create($object)
671
    {
672
        $this->prePersist($object);
673
        foreach ($this->extensions as $extension) {
674
            $extension->prePersist($this, $object);
675
        }
676
677
        $result = $this->getModelManager()->create($object);
678
        // BC compatibility
679
        if (null !== $result) {
680
            $object = $result;
681
        }
682
683
        $this->postPersist($object);
684
        foreach ($this->extensions as $extension) {
685
            $extension->postPersist($this, $object);
686
        }
687
688
        $this->createObjectSecurity($object);
689
690
        return $object;
691
    }
692
693
    public function delete($object)
694
    {
695
        $this->preRemove($object);
696
        foreach ($this->extensions as $extension) {
697
            $extension->preRemove($this, $object);
698
        }
699
700
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
701
        $this->getModelManager()->delete($object);
702
703
        $this->postRemove($object);
704
        foreach ($this->extensions as $extension) {
705
            $extension->postRemove($this, $object);
706
        }
707
    }
708
709
    public function preValidate($object)
0 ignored issues
show
Unused Code introduced by
The parameter $object 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...
710
    {
711
    }
712
713
    public function preUpdate($object)
714
    {
715
    }
716
717
    public function postUpdate($object)
718
    {
719
    }
720
721
    public function prePersist($object)
722
    {
723
    }
724
725
    public function postPersist($object)
726
    {
727
    }
728
729
    public function preRemove($object)
730
    {
731
    }
732
733
    public function postRemove($object)
734
    {
735
    }
736
737
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
738
    {
739
    }
740
741
    public function getFilterParameters()
742
    {
743
        $parameters = [];
744
745
        // build the values array
746
        if ($this->hasRequest()) {
747
            $filters = $this->request->query->get('filter', []);
748
749
            // if filter persistence is configured
750
            // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
751
            if (false !== $this->persistFilters && null !== $this->filterPersister) {
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$persistFilters has been deprecated with message: since 3.x, to be removed in 4.0.

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

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

Loading history...
752
                // if reset filters is asked, remove from storage
753
                if ('reset' === $this->request->query->get('filters')) {
754
                    $this->filterPersister->reset($this->getCode());
755
                }
756
757
                // if no filters, fetch from storage
758
                // otherwise save to storage
759
                if (empty($filters)) {
760
                    $filters = $this->filterPersister->get($this->getCode());
761
                } else {
762
                    $this->filterPersister->set($this->getCode(), $filters);
763
                }
764
            }
765
766
            $parameters = array_merge(
767
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
768
                $this->datagridValues,
769
                $this->getDefaultFilterValues(),
770
                $filters
771
            );
772
773
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
774
                $parameters['_per_page'] = $this->maxPerPage;
775
            }
776
777
            // always force the parent value
778
            if ($this->isChild() && $this->getParentAssociationMapping()) {
779
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
780
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
781
            }
782
        }
783
784
        return $parameters;
785
    }
786
787
    public function buildDatagrid()
788
    {
789
        if ($this->datagrid) {
790
            return;
791
        }
792
793
        $filterParameters = $this->getFilterParameters();
794
795
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
796
        if (isset($filterParameters['_sort_by']) && is_string($filterParameters['_sort_by'])) {
797
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
798
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
799
            } else {
800
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
801
                    $this->getClass(),
802
                    $filterParameters['_sort_by'],
803
                    []
804
                );
805
806
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
807
            }
808
        }
809
810
        // initialize the datagrid
811
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
812
813
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
814
815
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
816
817
        // build the datagrid filter
818
        $this->configureDatagridFilters($mapper);
819
820
        // ok, try to limit to add parent filter
821
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
822
            $mapper->add($this->getParentAssociationMapping(), null, [
823
                'show_filter' => false,
824
                'label' => false,
825
                'field_type' => ModelHiddenType::class,
826
                'field_options' => [
827
                    'model_manager' => $this->getModelManager(),
828
                ],
829
                'operator_type' => HiddenType::class,
830
            ], null, null, [
831
                'admin_code' => $this->getParent()->getCode(),
832
            ]);
833
        }
834
835
        foreach ($this->getExtensions() as $extension) {
836
            $extension->configureDatagridFilters($mapper);
837
        }
838
    }
839
840
    /**
841
     * Returns the name of the parent related field, so the field can be use to set the default
842
     * value (ie the parent object) or to filter the object.
843
     *
844
     * @return string the name of the parent related field
845
     */
846
    public function getParentAssociationMapping()
847
    {
848
        return $this->parentAssociationMapping;
849
    }
850
851
    /**
852
     * Returns the baseRoutePattern used to generate the routing information.
853
     *
854
     * @throws \RuntimeException
855
     *
856
     * @return string the baseRoutePattern used to generate the routing information
857
     */
858
    public function getBaseRoutePattern()
859
    {
860
        if (null !== $this->cachedBaseRoutePattern) {
861
            return $this->cachedBaseRoutePattern;
862
        }
863
864
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
865
            if (!$this->baseRoutePattern) {
866
                preg_match(self::CLASS_REGEX, $this->class, $matches);
867
868
                if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
869
                    throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
870
                }
871
            }
872
873
            $this->cachedBaseRoutePattern = sprintf('%s/%s/%s',
874
                $this->getParent()->getBaseRoutePattern(),
875
                $this->getParent()->getRouterIdParameter(),
876
                $this->baseRoutePattern ?: $this->urlize($matches[5], '-')
0 ignored issues
show
Bug introduced by
The variable $matches 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...
877
            );
878
        } elseif ($this->baseRoutePattern) {
879
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
880
        } else {
881
            preg_match(self::CLASS_REGEX, $this->class, $matches);
882
883
            if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
884
                throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
885
            }
886
887
            $this->cachedBaseRoutePattern = sprintf('/%s%s/%s',
888
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
889
                $this->urlize($matches[3], '-'),
890
                $this->urlize($matches[5], '-')
891
            );
892
        }
893
894
        return $this->cachedBaseRoutePattern;
895
    }
896
897
    /**
898
     * Returns the baseRouteName used to generate the routing information.
899
     *
900
     * @throws \RuntimeException
901
     *
902
     * @return string the baseRouteName used to generate the routing information
903
     */
904
    public function getBaseRouteName()
905
    {
906
        if (null !== $this->cachedBaseRouteName) {
907
            return $this->cachedBaseRouteName;
908
        }
909
910
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
911
            if (!$this->baseRouteName) {
912
                preg_match(self::CLASS_REGEX, $this->class, $matches);
913
914
                if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
915
                    throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
916
                }
917
            }
918
919
            $this->cachedBaseRouteName = sprintf('%s_%s',
920
                $this->getParent()->getBaseRouteName(),
921
                $this->baseRouteName ?: $this->urlize($matches[5])
0 ignored issues
show
Bug introduced by
The variable $matches 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...
922
            );
923
        } elseif ($this->baseRouteName) {
924
            $this->cachedBaseRouteName = $this->baseRouteName;
925
        } else {
926
            preg_match(self::CLASS_REGEX, $this->class, $matches);
927
928
            if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
929
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
930
            }
931
932
            $this->cachedBaseRouteName = sprintf('admin_%s%s_%s',
933
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
934
                $this->urlize($matches[3]),
935
                $this->urlize($matches[5])
936
            );
937
        }
938
939
        return $this->cachedBaseRouteName;
940
    }
941
942
    /**
943
     * urlize the given word.
944
     *
945
     * @param string $word
946
     * @param string $sep  the separator
947
     *
948
     * @return string
949
     */
950
    public function urlize($word, $sep = '_')
951
    {
952
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
953
    }
954
955
    public function getClass()
956
    {
957
        if ($this->hasActiveSubClass()) {
958
            if ($this->getParentFieldDescription()) {
959
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
960
            }
961
962
            $subClass = $this->getRequest()->query->get('subclass');
963
964
            if (!$this->hasSubClass($subClass)) {
965
                throw new \RuntimeException(sprintf('Subclass "%" is not defined.', $subClass));
966
            }
967
968
            return $this->getSubClass($subClass);
969
        }
970
971
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
972
        if ($this->subject && is_object($this->subject)) {
973
            return ClassUtils::getClass($this->subject);
974
        }
975
976
        return $this->class;
977
    }
978
979
    public function getSubClasses()
980
    {
981
        return $this->subClasses;
982
    }
983
984
    /**
985
     * NEXT_MAJOR: remove this method.
986
     */
987
    public function addSubClass($subClass)
988
    {
989
        @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...
990
            'Method "%s" is deprecated since 3.30 and will be removed in 4.0.',
991
            __METHOD__
992
        ), E_USER_DEPRECATED);
993
994
        if (!in_array($subClass, $this->subClasses)) {
995
            $this->subClasses[] = $subClass;
996
        }
997
    }
998
999
    public function setSubClasses(array $subClasses)
1000
    {
1001
        $this->subClasses = $subClasses;
1002
    }
1003
1004
    public function hasSubClass($name)
1005
    {
1006
        return isset($this->subClasses[$name]);
1007
    }
1008
1009
    public function hasActiveSubClass()
1010
    {
1011
        if (count($this->subClasses) > 0 && $this->request) {
1012
            return null !== $this->getRequest()->query->get('subclass');
1013
        }
1014
1015
        return false;
1016
    }
1017
1018
    public function getActiveSubClass()
1019
    {
1020
        if (!$this->hasActiveSubClass()) {
1021
            return;
1022
        }
1023
1024
        return $this->getSubClass($this->getActiveSubclassCode());
1025
    }
1026
1027
    public function getActiveSubclassCode()
1028
    {
1029
        if (!$this->hasActiveSubClass()) {
1030
            return;
1031
        }
1032
1033
        $subClass = $this->getRequest()->query->get('subclass');
1034
1035
        if (!$this->hasSubClass($subClass)) {
1036
            return;
1037
        }
1038
1039
        return $subClass;
1040
    }
1041
1042
    public function getBatchActions()
1043
    {
1044
        $actions = [];
1045
1046
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1047
            $actions['delete'] = [
1048
                'label' => 'action_delete',
1049
                'translation_domain' => 'SonataAdminBundle',
1050
                'ask_confirmation' => true, // by default always true
1051
            ];
1052
        }
1053
1054
        $actions = $this->configureBatchActions($actions);
1055
1056
        foreach ($this->getExtensions() as $extension) {
1057
            // TODO: remove method check in next major release
1058
            if (method_exists($extension, 'configureBatchActions')) {
1059
                $actions = $extension->configureBatchActions($this, $actions);
1060
            }
1061
        }
1062
1063
        foreach ($actions  as $name => &$action) {
1064
            if (!array_key_exists('label', $action)) {
1065
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1066
            }
1067
1068
            if (!array_key_exists('translation_domain', $action)) {
1069
                $action['translation_domain'] = $this->getTranslationDomain();
1070
            }
1071
        }
1072
1073
        return $actions;
1074
    }
1075
1076
    public function getRoutes()
1077
    {
1078
        $this->buildRoutes();
1079
1080
        return $this->routes;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->routes; (Sonata\AdminBundle\Route\RouteCollection) is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...torInterface::getRoutes of type Sonata\AdminBundle\Admin\RouteCollection.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1081
    }
1082
1083
    public function getRouterIdParameter()
1084
    {
1085
        return '{'.$this->getIdParameter().'}';
1086
    }
1087
1088
    public function getIdParameter()
1089
    {
1090
        $parameter = 'id';
1091
1092
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1093
            $parameter = 'child'.ucfirst($parameter);
1094
        }
1095
1096
        return $parameter;
1097
    }
1098
1099
    public function hasRoute($name)
1100
    {
1101
        if (!$this->routeGenerator) {
1102
            throw new \RuntimeException('RouteGenerator cannot be null');
1103
        }
1104
1105
        return $this->routeGenerator->hasAdminRoute($this, $name);
1106
    }
1107
1108
    public function isCurrentRoute($name, $adminCode = null)
1109
    {
1110
        if (!$this->hasRequest()) {
1111
            return false;
1112
        }
1113
1114
        $request = $this->getRequest();
1115
        $route = $request->get('_route');
1116
1117
        if ($adminCode) {
1118
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1119
        } else {
1120
            $admin = $this;
1121
        }
1122
1123
        if (!$admin) {
1124
            return false;
1125
        }
1126
1127
        return ($admin->getBaseRouteName().'_'.$name) == $route;
1128
    }
1129
1130
    public function generateObjectUrl($name, $object, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1131
    {
1132
        $parameters['id'] = $this->getUrlsafeIdentifier($object);
1133
1134
        return $this->generateUrl($name, $parameters, $absolute);
1135
    }
1136
1137
    public function generateUrl($name, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1138
    {
1139
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $absolute);
0 ignored issues
show
Documentation introduced by
$absolute is of type integer, but the function expects a boolean.

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...
1140
    }
1141
1142
    public function generateMenuUrl($name, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1143
    {
1144
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $absolute);
0 ignored issues
show
Documentation introduced by
$absolute is of type integer, but the function expects a boolean.

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...
1145
    }
1146
1147
    public function setTemplates(array $templates)
1148
    {
1149
        $this->templates = $templates;
1150
    }
1151
1152
    /**
1153
     * @param string $name
1154
     * @param string $template
1155
     */
1156
    public function setTemplate($name, $template)
1157
    {
1158
        $this->templates[$name] = $template;
1159
    }
1160
1161
    /**
1162
     * @return array
1163
     */
1164
    public function getTemplates()
1165
    {
1166
        return $this->templates;
1167
    }
1168
1169
    public function getTemplate($name)
1170
    {
1171
        if (isset($this->templates[$name])) {
1172
            return $this->templates[$name];
1173
        }
1174
    }
1175
1176
    public function getNewInstance()
1177
    {
1178
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1179
        foreach ($this->getExtensions() as $extension) {
1180
            $extension->alterNewInstance($this, $object);
1181
        }
1182
1183
        return $object;
1184
    }
1185
1186
    public function getFormBuilder()
1187
    {
1188
        $this->formOptions['data_class'] = $this->getClass();
1189
1190
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1191
            $this->getUniqid(),
1192
            $this->formOptions
1193
        );
1194
1195
        $this->defineFormBuilder($formBuilder);
1196
1197
        return $formBuilder;
1198
    }
1199
1200
    /**
1201
     * This method is being called by the main admin class and the child class,
1202
     * the getFormBuilder is only call by the main admin class.
1203
     */
1204
    public function defineFormBuilder(FormBuilderInterface $formBuilder)
1205
    {
1206
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1207
1208
        $this->configureFormFields($mapper);
1209
1210
        foreach ($this->getExtensions() as $extension) {
1211
            $extension->configureFormFields($mapper);
1212
        }
1213
1214
        $this->attachInlineValidator();
1215
    }
1216
1217
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
1218
    {
1219
        $pool = $this->getConfigurationPool();
1220
1221
        $adminCode = $fieldDescription->getOption('admin_code');
1222
1223
        if (null !== $adminCode) {
1224
            $admin = $pool->getAdminByAdminCode($adminCode);
0 ignored issues
show
Documentation introduced by
$adminCode 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...
1225
        } else {
1226
            $admin = $pool->getAdminByClass($fieldDescription->getTargetEntity());
1227
        }
1228
1229
        if (!$admin) {
1230
            return;
1231
        }
1232
1233
        if ($this->hasRequest()) {
1234
            $admin->setRequest($this->getRequest());
1235
        }
1236
1237
        $fieldDescription->setAssociationAdmin($admin);
1238
    }
1239
1240
    public function getObject($id)
1241
    {
1242
        $object = $this->getModelManager()->find($this->getClass(), $id);
1243
        foreach ($this->getExtensions() as $extension) {
1244
            $extension->alterObject($this, $object);
1245
        }
1246
1247
        return $object;
1248
    }
1249
1250
    public function getForm()
1251
    {
1252
        $this->buildForm();
1253
1254
        return $this->form;
1255
    }
1256
1257
    public function getList()
1258
    {
1259
        $this->buildList();
1260
1261
        return $this->list;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->list; (array) is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...istryInterface::getList of type Sonata\AdminBundle\Admin...ldDescriptionCollection.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1262
    }
1263
1264
    public function createQuery($context = 'list')
1265
    {
1266
        if (func_num_args() > 0) {
1267
            @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...
1268
                'The $context argument of '.__METHOD__.' is deprecated since 3.3, to be removed in 4.0.',
1269
                E_USER_DEPRECATED
1270
            );
1271
        }
1272
        $query = $this->getModelManager()->createQuery($this->getClass());
1273
1274
        foreach ($this->extensions as $extension) {
1275
            $extension->configureQuery($this, $query, $context);
1276
        }
1277
1278
        return $query;
1279
    }
1280
1281
    public function getDatagrid()
1282
    {
1283
        $this->buildDatagrid();
1284
1285
        return $this->datagrid;
1286
    }
1287
1288
    public function buildTabMenu($action, AdminInterface $childAdmin = null)
1289
    {
1290
        if ($this->loaded['tab_menu']) {
1291
            return;
1292
        }
1293
1294
        $this->loaded['tab_menu'] = true;
1295
1296
        $menu = $this->menuFactory->createItem('root');
1297
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1298
        $menu->setExtra('translation_domain', $this->translationDomain);
1299
1300
        // Prevents BC break with KnpMenuBundle v1.x
1301
        if (method_exists($menu, 'setCurrentUri')) {
1302
            $menu->setCurrentUri($this->getRequest()->getBaseUrl().$this->getRequest()->getPathInfo());
0 ignored issues
show
Bug introduced by
The method setCurrentUri() does not exist on Knp\Menu\ItemInterface. Did you maybe mean setCurrent()?

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...
1303
        }
1304
1305
        $this->configureTabMenu($menu, $action, $childAdmin);
1306
1307
        foreach ($this->getExtensions() as $extension) {
1308
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1309
        }
1310
1311
        $this->menu = $menu;
1312
    }
1313
1314
    public function buildSideMenu($action, AdminInterface $childAdmin = null)
1315
    {
1316
        return $this->buildTabMenu($action, $childAdmin);
1317
    }
1318
1319
    /**
1320
     * @param string $action
1321
     *
1322
     * @return ItemInterface
1323
     */
1324
    public function getSideMenu($action, AdminInterface $childAdmin = null)
1325
    {
1326
        if ($this->isChild()) {
1327
            return $this->getParent()->getSideMenu($action, $this);
1328
        }
1329
1330
        $this->buildSideMenu($action, $childAdmin);
1331
1332
        return $this->menu;
1333
    }
1334
1335
    /**
1336
     * Returns the root code.
1337
     *
1338
     * @return string the root code
1339
     */
1340
    public function getRootCode()
1341
    {
1342
        return $this->getRoot()->getCode();
1343
    }
1344
1345
    /**
1346
     * Returns the master admin.
1347
     *
1348
     * @return AbstractAdmin the root admin class
1349
     */
1350
    public function getRoot()
1351
    {
1352
        $parentFieldDescription = $this->getParentFieldDescription();
1353
1354
        if (!$parentFieldDescription) {
1355
            return $this;
1356
        }
1357
1358
        return $parentFieldDescription->getAdmin()->getRoot();
1359
    }
1360
1361
    public function setBaseControllerName($baseControllerName)
1362
    {
1363
        $this->baseControllerName = $baseControllerName;
1364
    }
1365
1366
    public function getBaseControllerName()
1367
    {
1368
        return $this->baseControllerName;
1369
    }
1370
1371
    /**
1372
     * @param string $label
1373
     */
1374
    public function setLabel($label)
1375
    {
1376
        $this->label = $label;
1377
    }
1378
1379
    public function getLabel()
1380
    {
1381
        return $this->label;
1382
    }
1383
1384
    /**
1385
     * @param bool $persist
1386
     *
1387
     * NEXT_MAJOR: remove this method
1388
     *
1389
     * @deprecated since 3.x, to be removed in 4.0.
1390
     */
1391
    public function setPersistFilters($persist)
1392
    {
1393
        @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...
1394
            'The '.__METHOD__.' method is deprecated since version 3.x and will be removed in 4.0.',
1395
            E_USER_DEPRECATED
1396
        );
1397
1398
        $this->persistFilters = $persist;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$persistFilters has been deprecated with message: since 3.x, to be removed in 4.0.

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

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

Loading history...
1399
    }
1400
1401
    /**
1402
     * @param FilterPersisterInterface|null $filterPersister
1403
     */
1404
    public function setFilterPersister(FilterPersisterInterface $filterPersister = null)
1405
    {
1406
        $this->filterPersister = $filterPersister;
1407
    }
1408
1409
    /**
1410
     * @param int $maxPerPage
1411
     */
1412
    public function setMaxPerPage($maxPerPage)
1413
    {
1414
        $this->maxPerPage = $maxPerPage;
1415
    }
1416
1417
    /**
1418
     * @return int
1419
     */
1420
    public function getMaxPerPage()
1421
    {
1422
        return $this->maxPerPage;
1423
    }
1424
1425
    /**
1426
     * @param int $maxPageLinks
1427
     */
1428
    public function setMaxPageLinks($maxPageLinks)
1429
    {
1430
        $this->maxPageLinks = $maxPageLinks;
1431
    }
1432
1433
    /**
1434
     * @return int
1435
     */
1436
    public function getMaxPageLinks()
1437
    {
1438
        return $this->maxPageLinks;
1439
    }
1440
1441
    public function getFormGroups()
1442
    {
1443
        return $this->formGroups;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->formGroups; of type array|boolean adds the type boolean to the return on line 1443 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getFormGroups of type array.
Loading history...
1444
    }
1445
1446
    public function setFormGroups(array $formGroups)
1447
    {
1448
        $this->formGroups = $formGroups;
1449
    }
1450
1451
    public function removeFieldFromFormGroup($key)
1452
    {
1453
        foreach ($this->formGroups as $name => $formGroup) {
0 ignored issues
show
Bug introduced by
The expression $this->formGroups of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1454
            unset($this->formGroups[$name]['fields'][$key]);
1455
1456
            if (empty($this->formGroups[$name]['fields'])) {
1457
                unset($this->formGroups[$name]);
1458
            }
1459
        }
1460
    }
1461
1462
    /**
1463
     * @param array $group
1464
     */
1465
    public function reorderFormGroup($group, array $keys)
1466
    {
1467
        $formGroups = $this->getFormGroups();
1468
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1469
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups() on line 1467 can also be of type boolean; however, Sonata\AdminBundle\Admin...tAdmin::setFormGroups() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1470
    }
1471
1472
    public function getFormTabs()
1473
    {
1474
        return $this->formTabs;
1475
    }
1476
1477
    public function setFormTabs(array $formTabs)
1478
    {
1479
        $this->formTabs = $formTabs;
1480
    }
1481
1482
    public function getShowTabs()
1483
    {
1484
        return $this->showTabs;
1485
    }
1486
1487
    public function setShowTabs(array $showTabs)
1488
    {
1489
        $this->showTabs = $showTabs;
1490
    }
1491
1492
    public function getShowGroups()
1493
    {
1494
        return $this->showGroups;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->showGroups; of type array|boolean adds the type boolean to the return on line 1494 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getShowGroups of type array.
Loading history...
1495
    }
1496
1497
    public function setShowGroups(array $showGroups)
1498
    {
1499
        $this->showGroups = $showGroups;
1500
    }
1501
1502
    public function reorderShowGroup($group, array $keys)
1503
    {
1504
        $showGroups = $this->getShowGroups();
1505
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1506
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups() on line 1504 can also be of type boolean; however, Sonata\AdminBundle\Admin...tAdmin::setShowGroups() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1507
    }
1508
1509
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
1510
    {
1511
        $this->parentFieldDescription = $parentFieldDescription;
1512
    }
1513
1514
    public function getParentFieldDescription()
1515
    {
1516
        return $this->parentFieldDescription;
1517
    }
1518
1519
    public function hasParentFieldDescription()
1520
    {
1521
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1522
    }
1523
1524
    public function setSubject($subject)
1525
    {
1526
        if (is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1527
            $message = <<<'EOT'
1528
You are trying to set entity an instance of "%s",
1529
which is not the one registered with this admin class ("%s").
1530
This is deprecated since 3.5 and will no longer be supported in 4.0.
1531
EOT;
1532
1533
            @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...
1534
                sprintf($message, get_class($subject), $this->getClass()),
1535
                E_USER_DEPRECATED
1536
            ); // NEXT_MAJOR : throw an exception instead
1537
        }
1538
1539
        $this->subject = $subject;
1540
    }
1541
1542
    public function getSubject()
1543
    {
1544
        if (null === $this->subject && $this->request && !$this->hasParentFieldDescription()) {
1545
            $id = $this->request->get($this->getIdParameter());
1546
1547
            if (null !== $id) {
1548
                $this->subject = $this->getObject($id);
1549
            }
1550
        }
1551
1552
        return $this->subject;
1553
    }
1554
1555
    public function hasSubject()
1556
    {
1557
        return (bool) $this->getSubject();
1558
    }
1559
1560
    public function getFormFieldDescriptions()
1561
    {
1562
        $this->buildForm();
1563
1564
        return $this->formFieldDescriptions;
1565
    }
1566
1567
    public function getFormFieldDescription($name)
1568
    {
1569
        return $this->hasFormFieldDescription($name) ? $this->formFieldDescriptions[$name] : null;
1570
    }
1571
1572
    /**
1573
     * Returns true if the admin has a FieldDescription with the given $name.
1574
     *
1575
     * @param string $name
1576
     *
1577
     * @return bool
1578
     */
1579
    public function hasFormFieldDescription($name)
1580
    {
1581
        return array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1582
    }
1583
1584
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1585
    {
1586
        $this->formFieldDescriptions[$name] = $fieldDescription;
1587
    }
1588
1589
    /**
1590
     * remove a FieldDescription.
1591
     *
1592
     * @param string $name
1593
     */
1594
    public function removeFormFieldDescription($name)
1595
    {
1596
        unset($this->formFieldDescriptions[$name]);
1597
    }
1598
1599
    /**
1600
     * build and return the collection of form FieldDescription.
1601
     *
1602
     * @return array collection of form FieldDescription
1603
     */
1604
    public function getShowFieldDescriptions()
1605
    {
1606
        $this->buildShow();
1607
1608
        return $this->showFieldDescriptions;
1609
    }
1610
1611
    /**
1612
     * Returns the form FieldDescription with the given $name.
1613
     *
1614
     * @param string $name
1615
     *
1616
     * @return FieldDescriptionInterface
1617
     */
1618
    public function getShowFieldDescription($name)
1619
    {
1620
        $this->buildShow();
1621
1622
        return $this->hasShowFieldDescription($name) ? $this->showFieldDescriptions[$name] : null;
1623
    }
1624
1625
    public function hasShowFieldDescription($name)
1626
    {
1627
        return array_key_exists($name, $this->showFieldDescriptions);
1628
    }
1629
1630
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1631
    {
1632
        $this->showFieldDescriptions[$name] = $fieldDescription;
1633
    }
1634
1635
    public function removeShowFieldDescription($name)
1636
    {
1637
        unset($this->showFieldDescriptions[$name]);
1638
    }
1639
1640
    public function getListFieldDescriptions()
1641
    {
1642
        $this->buildList();
1643
1644
        return $this->listFieldDescriptions;
1645
    }
1646
1647
    public function getListFieldDescription($name)
1648
    {
1649
        return $this->hasListFieldDescription($name) ? $this->listFieldDescriptions[$name] : null;
1650
    }
1651
1652
    public function hasListFieldDescription($name)
1653
    {
1654
        $this->buildList();
1655
1656
        return array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1657
    }
1658
1659
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1660
    {
1661
        $this->listFieldDescriptions[$name] = $fieldDescription;
1662
    }
1663
1664
    public function removeListFieldDescription($name)
1665
    {
1666
        unset($this->listFieldDescriptions[$name]);
1667
    }
1668
1669
    public function getFilterFieldDescription($name)
1670
    {
1671
        return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null;
1672
    }
1673
1674
    public function hasFilterFieldDescription($name)
1675
    {
1676
        return array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1677
    }
1678
1679
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1680
    {
1681
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1682
    }
1683
1684
    public function removeFilterFieldDescription($name)
1685
    {
1686
        unset($this->filterFieldDescriptions[$name]);
1687
    }
1688
1689
    public function getFilterFieldDescriptions()
1690
    {
1691
        $this->buildDatagrid();
1692
1693
        return $this->filterFieldDescriptions;
1694
    }
1695
1696
    public function addChild(AdminInterface $child)
1697
    {
1698
        for ($parentAdmin = $this; null !== $parentAdmin; $parentAdmin = $parentAdmin->getParent()) {
1699
            if ($parentAdmin->getCode() !== $child->getCode()) {
1700
                continue;
1701
            }
1702
1703
            throw new \RuntimeException(sprintf(
1704
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1705
                $child->getCode(), $this->getCode()
1706
            ));
1707
        }
1708
1709
        $this->children[$child->getCode()] = $child;
1710
1711
        $child->setParent($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Sonata\AdminBundle\Admin\AbstractAdmin>, but the function expects a object<self>.

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...
1712
    }
1713
1714
    public function hasChild($code)
1715
    {
1716
        return isset($this->children[$code]);
1717
    }
1718
1719
    public function getChildren()
1720
    {
1721
        return $this->children;
1722
    }
1723
1724
    public function getChild($code)
1725
    {
1726
        return $this->hasChild($code) ? $this->children[$code] : null;
1727
    }
1728
1729
    public function setParent(AdminInterface $parent)
1730
    {
1731
        $this->parent = $parent;
1732
    }
1733
1734
    public function getParent()
1735
    {
1736
        return $this->parent;
1737
    }
1738
1739
    final public function getRootAncestor()
1740
    {
1741
        $parent = $this;
1742
1743
        while ($parent->isChild()) {
1744
            $parent = $parent->getParent();
1745
        }
1746
1747
        return $parent;
1748
    }
1749
1750
    final public function getChildDepth()
1751
    {
1752
        $parent = $this;
1753
        $depth = 0;
1754
1755
        while ($parent->isChild()) {
1756
            $parent = $parent->getParent();
1757
            ++$depth;
1758
        }
1759
1760
        return $depth;
1761
    }
1762
1763
    final public function getCurrentLeafChildAdmin()
1764
    {
1765
        $child = $this->getCurrentChildAdmin();
1766
1767
        if (null === $child) {
1768
            return;
1769
        }
1770
1771
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1772
            $child = $c;
1773
        }
1774
1775
        return $child;
1776
    }
1777
1778
    public function isChild()
1779
    {
1780
        return $this->parent instanceof AdminInterface;
1781
    }
1782
1783
    /**
1784
     * Returns true if the admin has children, false otherwise.
1785
     *
1786
     * @return bool if the admin has children
1787
     */
1788
    public function hasChildren()
1789
    {
1790
        return count($this->children) > 0;
1791
    }
1792
1793
    public function setUniqid($uniqid)
1794
    {
1795
        $this->uniqid = $uniqid;
1796
    }
1797
1798
    public function getUniqid()
1799
    {
1800
        if (!$this->uniqid) {
1801
            $this->uniqid = 's'.uniqid();
1802
        }
1803
1804
        return $this->uniqid;
1805
    }
1806
1807
    /**
1808
     * Returns the classname label.
1809
     *
1810
     * @return string the classname label
1811
     */
1812
    public function getClassnameLabel()
1813
    {
1814
        return $this->classnameLabel;
1815
    }
1816
1817
    public function getPersistentParameters()
1818
    {
1819
        $parameters = [];
1820
1821
        foreach ($this->getExtensions() as $extension) {
1822
            $params = $extension->getPersistentParameters($this);
1823
1824
            if (!is_array($params)) {
1825
                throw new \RuntimeException(sprintf('The %s::getPersistentParameters must return an array', get_class($extension)));
1826
            }
1827
1828
            $parameters = array_merge($parameters, $params);
1829
        }
1830
1831
        return $parameters;
1832
    }
1833
1834
    /**
1835
     * @param string $name
1836
     *
1837
     * @return null|mixed
1838
     */
1839
    public function getPersistentParameter($name)
1840
    {
1841
        $parameters = $this->getPersistentParameters();
1842
1843
        return isset($parameters[$name]) ? $parameters[$name] : null;
1844
    }
1845
1846
    public function getBreadcrumbs($action)
1847
    {
1848
        @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...
1849
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
1850
            ' Use Sonata\AdminBundle\Admin\BreadcrumbsBuilder::getBreadcrumbs instead.',
1851
            E_USER_DEPRECATED
1852
        );
1853
1854
        return $this->getBreadcrumbsBuilder()->getBreadcrumbs($this, $action);
1855
    }
1856
1857
    /**
1858
     * Generates the breadcrumbs array.
1859
     *
1860
     * Note: the method will be called by the top admin instance (parent => child)
1861
     *
1862
     * @param string $action
1863
     *
1864
     * @return array
1865
     */
1866
    public function buildBreadcrumbs($action, MenuItemInterface $menu = null)
1867
    {
1868
        @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...
1869
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.',
1870
            E_USER_DEPRECATED
1871
        );
1872
1873
        if (isset($this->breadcrumbs[$action])) {
1874
            return $this->breadcrumbs[$action];
1875
        }
1876
1877
        return $this->breadcrumbs[$action] = $this->getBreadcrumbsBuilder()
1878
            ->buildBreadcrumbs($this, $action, $menu);
1879
    }
1880
1881
    /**
1882
     * NEXT_MAJOR : remove this method.
1883
     *
1884
     * @return BreadcrumbsBuilderInterface
1885
     */
1886
    final public function getBreadcrumbsBuilder()
1887
    {
1888
        @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...
1889
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
1890
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
1891
            E_USER_DEPRECATED
1892
        );
1893
        if (null === $this->breadcrumbsBuilder) {
1894
            $this->breadcrumbsBuilder = new BreadcrumbsBuilder(
1895
                $this->getConfigurationPool()->getContainer()->getParameter('sonata.admin.configuration.breadcrumbs')
1896
            );
1897
        }
1898
1899
        return $this->breadcrumbsBuilder;
1900
    }
1901
1902
    /**
1903
     * NEXT_MAJOR : remove this method.
1904
     *
1905
     * @return AbstractAdmin
1906
     */
1907
    final public function setBreadcrumbsBuilder(BreadcrumbsBuilderInterface $value)
1908
    {
1909
        @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...
1910
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
1911
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
1912
            E_USER_DEPRECATED
1913
        );
1914
        $this->breadcrumbsBuilder = $value;
1915
1916
        return $this;
1917
    }
1918
1919
    public function setCurrentChild($currentChild)
1920
    {
1921
        $this->currentChild = $currentChild;
1922
    }
1923
1924
    public function getCurrentChild()
1925
    {
1926
        return $this->currentChild;
1927
    }
1928
1929
    /**
1930
     * Returns the current child admin instance.
1931
     *
1932
     * @return AdminInterface|null the current child admin instance
1933
     */
1934
    public function getCurrentChildAdmin()
1935
    {
1936
        foreach ($this->children as $children) {
1937
            if ($children->getCurrentChild()) {
1938
                return $children;
1939
            }
1940
        }
1941
    }
1942
1943
    public function trans($id, array $parameters = [], $domain = null, $locale = null)
1944
    {
1945
        @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...
1946
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
1947
            E_USER_DEPRECATED
1948
        );
1949
1950
        $domain = $domain ?: $this->getTranslationDomain();
1951
1952
        return $this->translator->trans($id, $parameters, $domain, $locale);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since 3.9, to be removed with 4.0

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

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

Loading history...
1953
    }
1954
1955
    /**
1956
     * Translate a message id.
1957
     *
1958
     * NEXT_MAJOR: remove this method
1959
     *
1960
     * @param string      $id
1961
     * @param int         $count
1962
     * @param string|null $domain
1963
     * @param string|null $locale
1964
     *
1965
     * @return string the translated string
1966
     *
1967
     * @deprecated since 3.9, to be removed with 4.0
1968
     */
1969
    public function transChoice($id, $count, array $parameters = [], $domain = null, $locale = null)
1970
    {
1971
        @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...
1972
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
1973
            E_USER_DEPRECATED
1974
        );
1975
1976
        $domain = $domain ?: $this->getTranslationDomain();
1977
1978
        return $this->translator->transChoice($id, $count, $parameters, $domain, $locale);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since 3.9, to be removed with 4.0

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

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

Loading history...
1979
    }
1980
1981
    public function setTranslationDomain($translationDomain)
1982
    {
1983
        $this->translationDomain = $translationDomain;
1984
    }
1985
1986
    public function getTranslationDomain()
1987
    {
1988
        return $this->translationDomain;
1989
    }
1990
1991
    /**
1992
     * {@inheritdoc}
1993
     *
1994
     * NEXT_MAJOR: remove this method
1995
     *
1996
     * @deprecated since 3.9, to be removed with 4.0
1997
     */
1998
    public function setTranslator(TranslatorInterface $translator)
1999
    {
2000
        $args = func_get_args();
2001
        if (isset($args[1]) && $args[1]) {
2002
            @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...
2003
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2004
                E_USER_DEPRECATED
2005
            );
2006
        }
2007
2008
        $this->translator = $translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since 3.9, to be removed with 4.0

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

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

Loading history...
2009
    }
2010
2011
    /**
2012
     * {@inheritdoc}
2013
     *
2014
     * NEXT_MAJOR: remove this method
2015
     *
2016
     * @deprecated since 3.9, to be removed with 4.0
2017
     */
2018
    public function getTranslator()
2019
    {
2020
        @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...
2021
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2022
            E_USER_DEPRECATED
2023
        );
2024
2025
        return $this->translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since 3.9, to be removed with 4.0

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

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

Loading history...
2026
    }
2027
2028
    public function getTranslationLabel($label, $context = '', $type = '')
2029
    {
2030
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2031
    }
2032
2033
    public function setRequest(Request $request)
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
2034
    {
2035
        $this->request = $request;
2036
2037
        foreach ($this->getChildren() as $children) {
2038
            $children->setRequest($request);
2039
        }
2040
    }
2041
2042
    public function getRequest()
2043
    {
2044
        if (!$this->request) {
2045
            throw new \RuntimeException('The Request object has not been set');
2046
        }
2047
2048
        return $this->request;
2049
    }
2050
2051
    public function hasRequest()
2052
    {
2053
        return null !== $this->request;
2054
    }
2055
2056
    public function setFormContractor(FormContractorInterface $formBuilder)
2057
    {
2058
        $this->formContractor = $formBuilder;
2059
    }
2060
2061
    /**
2062
     * @return FormContractorInterface
2063
     */
2064
    public function getFormContractor()
2065
    {
2066
        return $this->formContractor;
2067
    }
2068
2069
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder)
2070
    {
2071
        $this->datagridBuilder = $datagridBuilder;
2072
    }
2073
2074
    public function getDatagridBuilder()
2075
    {
2076
        return $this->datagridBuilder;
2077
    }
2078
2079
    public function setListBuilder(ListBuilderInterface $listBuilder)
2080
    {
2081
        $this->listBuilder = $listBuilder;
2082
    }
2083
2084
    public function getListBuilder()
2085
    {
2086
        return $this->listBuilder;
2087
    }
2088
2089
    public function setShowBuilder(ShowBuilderInterface $showBuilder)
2090
    {
2091
        $this->showBuilder = $showBuilder;
2092
    }
2093
2094
    /**
2095
     * @return ShowBuilderInterface
2096
     */
2097
    public function getShowBuilder()
2098
    {
2099
        return $this->showBuilder;
2100
    }
2101
2102
    public function setConfigurationPool(Pool $configurationPool)
2103
    {
2104
        $this->configurationPool = $configurationPool;
2105
    }
2106
2107
    /**
2108
     * @return Pool
2109
     */
2110
    public function getConfigurationPool()
2111
    {
2112
        return $this->configurationPool;
2113
    }
2114
2115
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator)
2116
    {
2117
        $this->routeGenerator = $routeGenerator;
2118
    }
2119
2120
    /**
2121
     * @return RouteGeneratorInterface
2122
     */
2123
    public function getRouteGenerator()
2124
    {
2125
        return $this->routeGenerator;
2126
    }
2127
2128
    public function getCode()
2129
    {
2130
        return $this->code;
2131
    }
2132
2133
    /**
2134
     * NEXT_MAJOR: Remove this function.
2135
     *
2136
     * @deprecated This method is deprecated since 3.24 and will be removed in 4.0
2137
     *
2138
     * @param string $baseCodeRoute
2139
     */
2140
    public function setBaseCodeRoute($baseCodeRoute)
2141
    {
2142
        @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...
2143
            'The '.__METHOD__.' is deprecated since 3.24 and will be removed in 4.0.',
2144
            E_USER_DEPRECATED
2145
        );
2146
2147
        $this->baseCodeRoute = $baseCodeRoute;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since 3.24 and will be removed in 4.0

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

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

Loading history...
2148
    }
2149
2150
    public function getBaseCodeRoute()
2151
    {
2152
        // NEXT_MAJOR: Uncomment the following lines.
2153
        // if ($this->isChild()) {
2154
        //     return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2155
        // }
2156
        //
2157
        // return $this->getCode();
2158
2159
        // NEXT_MAJOR: Remove all the code below.
2160
        if ($this->isChild()) {
2161
            $parentCode = $this->getParent()->getCode();
2162
2163
            if ($this->getParent()->isChild()) {
2164
                $parentCode = $this->getParent()->getBaseCodeRoute();
2165
            }
2166
2167
            return $parentCode.'|'.$this->getCode();
2168
        }
2169
2170
        return $this->baseCodeRoute;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since 3.24 and will be removed in 4.0

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

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

Loading history...
2171
    }
2172
2173
    public function getModelManager()
2174
    {
2175
        return $this->modelManager;
2176
    }
2177
2178
    public function setModelManager(ModelManagerInterface $modelManager)
2179
    {
2180
        $this->modelManager = $modelManager;
2181
    }
2182
2183
    public function getManagerType()
2184
    {
2185
        return $this->managerType;
2186
    }
2187
2188
    /**
2189
     * @param string $type
2190
     */
2191
    public function setManagerType($type)
2192
    {
2193
        $this->managerType = $type;
2194
    }
2195
2196
    public function getObjectIdentifier()
2197
    {
2198
        return $this->getCode();
2199
    }
2200
2201
    /**
2202
     * Set the roles and permissions per role.
2203
     */
2204
    public function setSecurityInformation(array $information)
2205
    {
2206
        $this->securityInformation = $information;
2207
    }
2208
2209
    public function getSecurityInformation()
2210
    {
2211
        return $this->securityInformation;
2212
    }
2213
2214
    /**
2215
     * Return the list of permissions the user should have in order to display the admin.
2216
     *
2217
     * @param string $context
2218
     *
2219
     * @return array
2220
     */
2221
    public function getPermissionsShow($context)
2222
    {
2223
        switch ($context) {
2224
            case self::CONTEXT_DASHBOARD:
2225
            case self::CONTEXT_MENU:
2226
            default:
2227
                return ['LIST'];
2228
        }
2229
    }
2230
2231
    public function showIn($context)
2232
    {
2233
        switch ($context) {
2234
            case self::CONTEXT_DASHBOARD:
2235
            case self::CONTEXT_MENU:
2236
            default:
2237
                return $this->isGranted($this->getPermissionsShow($context));
0 ignored issues
show
Documentation introduced by
$this->getPermissionsShow($context) is of type array<integer,string,{"0":"string"}>, 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...
2238
        }
2239
    }
2240
2241
    public function createObjectSecurity($object)
2242
    {
2243
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2244
    }
2245
2246
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
2247
    {
2248
        $this->securityHandler = $securityHandler;
2249
    }
2250
2251
    public function getSecurityHandler()
2252
    {
2253
        return $this->securityHandler;
2254
    }
2255
2256
    public function isGranted($name, $object = null)
2257
    {
2258
        $key = md5(json_encode($name).($object ? '/'.spl_object_hash($object) : ''));
2259
2260
        if (!array_key_exists($key, $this->cacheIsGranted)) {
2261
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2262
        }
2263
2264
        return $this->cacheIsGranted[$key];
2265
    }
2266
2267
    public function getUrlsafeIdentifier($entity)
2268
    {
2269
        return $this->getModelManager()->getUrlsafeIdentifier($entity);
2270
    }
2271
2272
    public function getNormalizedIdentifier($entity)
2273
    {
2274
        return $this->getModelManager()->getNormalizedIdentifier($entity);
2275
    }
2276
2277
    public function id($entity)
2278
    {
2279
        return $this->getNormalizedIdentifier($entity);
2280
    }
2281
2282
    public function setValidator($validator)
2283
    {
2284
        // NEXT_MAJOR: Move ValidatorInterface check to method signature
2285
        if (!$validator instanceof ValidatorInterface) {
2286
            throw new \InvalidArgumentException(
2287
                'Argument 1 must be an instance of Symfony\Component\Validator\Validator\ValidatorInterface'
2288
            );
2289
        }
2290
2291
        $this->validator = $validator;
2292
    }
2293
2294
    public function getValidator()
2295
    {
2296
        return $this->validator;
2297
    }
2298
2299
    public function getShow()
2300
    {
2301
        $this->buildShow();
2302
2303
        return $this->show;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->show; (Sonata\AdminBundle\Admin...ldDescriptionCollection) is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin\AdminInterface::getShow of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
2304
    }
2305
2306
    public function setFormTheme(array $formTheme)
2307
    {
2308
        $this->formTheme = $formTheme;
2309
    }
2310
2311
    public function getFormTheme()
2312
    {
2313
        return $this->formTheme;
2314
    }
2315
2316
    public function setFilterTheme(array $filterTheme)
2317
    {
2318
        $this->filterTheme = $filterTheme;
2319
    }
2320
2321
    public function getFilterTheme()
2322
    {
2323
        return $this->filterTheme;
2324
    }
2325
2326
    public function addExtension(AdminExtensionInterface $extension)
2327
    {
2328
        $this->extensions[] = $extension;
2329
    }
2330
2331
    public function getExtensions()
2332
    {
2333
        return $this->extensions;
2334
    }
2335
2336
    public function setMenuFactory(MenuFactoryInterface $menuFactory)
2337
    {
2338
        $this->menuFactory = $menuFactory;
2339
    }
2340
2341
    public function getMenuFactory()
2342
    {
2343
        return $this->menuFactory;
2344
    }
2345
2346
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder)
2347
    {
2348
        $this->routeBuilder = $routeBuilder;
2349
    }
2350
2351
    public function getRouteBuilder()
2352
    {
2353
        return $this->routeBuilder;
2354
    }
2355
2356
    public function toString($object)
2357
    {
2358
        if (!is_object($object)) {
2359
            return '';
2360
        }
2361
2362
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2363
            return (string) $object;
2364
        }
2365
2366
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2367
    }
2368
2369
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy)
2370
    {
2371
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2372
    }
2373
2374
    public function getLabelTranslatorStrategy()
2375
    {
2376
        return $this->labelTranslatorStrategy;
2377
    }
2378
2379
    public function supportsPreviewMode()
2380
    {
2381
        return $this->supportsPreviewMode;
2382
    }
2383
2384
    /**
2385
     * Set custom per page options.
2386
     */
2387
    public function setPerPageOptions(array $options)
2388
    {
2389
        $this->perPageOptions = $options;
2390
    }
2391
2392
    /**
2393
     * Returns predefined per page options.
2394
     *
2395
     * @return array
2396
     */
2397
    public function getPerPageOptions()
2398
    {
2399
        return $this->perPageOptions;
2400
    }
2401
2402
    /**
2403
     * Set pager type.
2404
     *
2405
     * @param string $pagerType
2406
     */
2407
    public function setPagerType($pagerType)
2408
    {
2409
        $this->pagerType = $pagerType;
2410
    }
2411
2412
    /**
2413
     * Get pager type.
2414
     *
2415
     * @return string
2416
     */
2417
    public function getPagerType()
2418
    {
2419
        return $this->pagerType;
2420
    }
2421
2422
    /**
2423
     * Returns true if the per page value is allowed, false otherwise.
2424
     *
2425
     * @param int $perPage
2426
     *
2427
     * @return bool
2428
     */
2429
    public function determinedPerPageValue($perPage)
2430
    {
2431
        return in_array($perPage, $this->perPageOptions);
2432
    }
2433
2434
    public function isAclEnabled()
2435
    {
2436
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2437
    }
2438
2439
    public function getObjectMetadata($object)
2440
    {
2441
        return new Metadata($this->toString($object));
2442
    }
2443
2444
    public function getListModes()
2445
    {
2446
        return $this->listModes;
2447
    }
2448
2449
    public function setListMode($mode)
2450
    {
2451
        if (!$this->hasRequest()) {
2452
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2453
        }
2454
2455
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2456
    }
2457
2458
    public function getListMode()
2459
    {
2460
        if (!$this->hasRequest()) {
2461
            return 'list';
2462
        }
2463
2464
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2465
    }
2466
2467
    public function getAccessMapping()
2468
    {
2469
        return $this->accessMapping;
2470
    }
2471
2472
    public function checkAccess($action, $object = null)
2473
    {
2474
        $access = $this->getAccess();
2475
2476
        if (!array_key_exists($action, $access)) {
2477
            throw new \InvalidArgumentException(sprintf(
2478
                'Action "%s" could not be found in access mapping.'
2479
                .' Please make sure your action is defined into your admin class accessMapping property.',
2480
                $action
2481
            ));
2482
        }
2483
2484
        if (!is_array($access[$action])) {
2485
            $access[$action] = [$access[$action]];
2486
        }
2487
2488
        foreach ($access[$action] as $role) {
2489
            if (false === $this->isGranted($role, $object)) {
2490
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2491
            }
2492
        }
2493
    }
2494
2495
    /**
2496
     * Hook to handle access authorization, without throw Exception.
2497
     *
2498
     * @param string $action
2499
     * @param object $object
2500
     *
2501
     * @return bool
2502
     */
2503
    public function hasAccess($action, $object = null)
2504
    {
2505
        $access = $this->getAccess();
2506
2507
        if (!array_key_exists($action, $access)) {
2508
            return false;
2509
        }
2510
2511
        if (!is_array($access[$action])) {
2512
            $access[$action] = [$access[$action]];
2513
        }
2514
2515
        foreach ($access[$action] as $role) {
2516
            if (false === $this->isGranted($role, $object)) {
2517
                return false;
2518
            }
2519
        }
2520
2521
        return true;
2522
    }
2523
2524
    public function configureActionButtons($action, $object = null)
2525
    {
2526
        $list = [];
2527
2528
        if (in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'])
2529
            && $this->hasAccess('create')
2530
            && $this->hasRoute('create')
2531
        ) {
2532
            $list['create'] = [
2533
                'template' => $this->getTemplate('button_create'),
2534
            ];
2535
        }
2536
2537
        if (in_array($action, ['show', 'delete', 'acl', 'history'])
2538
            && $this->canAccessObject('edit', $object)
2539
            && $this->hasRoute('edit')
2540
        ) {
2541
            $list['edit'] = [
2542
                'template' => $this->getTemplate('button_edit'),
2543
            ];
2544
        }
2545
2546
        if (in_array($action, ['show', 'edit', 'acl'])
2547
            && $this->canAccessObject('history', $object)
2548
            && $this->hasRoute('history')
2549
        ) {
2550
            $list['history'] = [
2551
                'template' => $this->getTemplate('button_history'),
2552
            ];
2553
        }
2554
2555
        if (in_array($action, ['edit', 'history'])
2556
            && $this->isAclEnabled()
2557
            && $this->canAccessObject('acl', $object)
2558
            && $this->hasRoute('acl')
2559
        ) {
2560
            $list['acl'] = [
2561
                'template' => $this->getTemplate('button_acl'),
2562
            ];
2563
        }
2564
2565
        if (in_array($action, ['edit', 'history', 'acl'])
2566
            && $this->canAccessObject('show', $object)
2567
            && count($this->getShow()) > 0
2568
            && $this->hasRoute('show')
2569
        ) {
2570
            $list['show'] = [
2571
                'template' => $this->getTemplate('button_show'),
2572
            ];
2573
        }
2574
2575
        if (in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'])
2576
            && $this->hasAccess('list')
2577
            && $this->hasRoute('list')
2578
        ) {
2579
            $list['list'] = [
2580
                'template' => $this->getTemplate('button_list'),
2581
            ];
2582
        }
2583
2584
        return $list;
2585
    }
2586
2587
    /**
2588
     * @param string $action
2589
     * @param mixed  $object
2590
     *
2591
     * @return array
2592
     */
2593
    public function getActionButtons($action, $object = null)
2594
    {
2595
        $list = $this->configureActionButtons($action, $object);
2596
2597
        foreach ($this->getExtensions() as $extension) {
2598
            // TODO: remove method check in next major release
2599
            if (method_exists($extension, 'configureActionButtons')) {
2600
                $list = $extension->configureActionButtons($this, $list, $action, $object);
2601
            }
2602
        }
2603
2604
        return $list;
2605
    }
2606
2607
    /**
2608
     * Get the list of actions that can be accessed directly from the dashboard.
2609
     *
2610
     * @return array
2611
     */
2612
    public function getDashboardActions()
2613
    {
2614
        $actions = [];
2615
2616
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2617
            $actions['create'] = [
2618
                'label' => 'link_add',
2619
                'translation_domain' => 'SonataAdminBundle',
2620
                'template' => $this->getTemplate('action_create'),
2621
                'url' => $this->generateUrl('create'),
2622
                'icon' => 'plus-circle',
2623
            ];
2624
        }
2625
2626
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2627
            $actions['list'] = [
2628
                'label' => 'link_list',
2629
                'translation_domain' => 'SonataAdminBundle',
2630
                'url' => $this->generateUrl('list'),
2631
                'icon' => 'list',
2632
            ];
2633
        }
2634
2635
        return $actions;
2636
    }
2637
2638
    /**
2639
     * Setting to true will enable mosaic button for the admin screen.
2640
     * Setting to false will hide mosaic button for the admin screen.
2641
     *
2642
     * @param bool $isShown
2643
     */
2644
    final public function showMosaicButton($isShown)
2645
    {
2646
        if ($isShown) {
2647
            $this->listModes['mosaic'] = ['class' => self::MOSAIC_ICON_CLASS];
2648
        } else {
2649
            unset($this->listModes['mosaic']);
2650
        }
2651
    }
2652
2653
    /**
2654
     * @param FormMapper $form
0 ignored issues
show
Bug introduced by
There is no parameter named $form. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
2655
     */
2656
    final public function getSearchResultLink($object)
2657
    {
2658
        foreach ($this->searchResultActions as $action) {
2659
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2660
                return $this->generateObjectUrl($action, $object);
2661
            }
2662
        }
2663
    }
2664
2665
    /**
2666
     * Checks if a filter type is set to a default value.
2667
     *
2668
     * @param string $name
2669
     *
2670
     * @return bool
2671
     */
2672
    final public function isDefaultFilter($name)
2673
    {
2674
        $filter = $this->getFilterParameters();
2675
        $default = $this->getDefaultFilterValues();
2676
2677
        if (!array_key_exists($name, $filter) || !array_key_exists($name, $default)) {
2678
            return false;
2679
        }
2680
2681
        return $filter[$name] == $default[$name];
2682
    }
2683
2684
    /**
2685
     * Check object existence and access, without throw Exception.
2686
     *
2687
     * @param string $action
2688
     * @param object $object
2689
     *
2690
     * @return bool
2691
     */
2692
    public function canAccessObject($action, $object)
2693
    {
2694
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2695
    }
2696
2697
    /**
2698
     * Returns a list of default filters.
2699
     *
2700
     * @return array
2701
     */
2702
    final protected function getDefaultFilterValues()
2703
    {
2704
        $defaultFilterValues = [];
2705
2706
        $this->configureDefaultFilterValues($defaultFilterValues);
2707
2708
        foreach ($this->getExtensions() as $extension) {
2709
            // NEXT_MAJOR: remove method check in next major release
2710
            if (method_exists($extension, 'configureDefaultFilterValues')) {
2711
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2712
            }
2713
        }
2714
2715
        return $defaultFilterValues;
2716
    }
2717
2718
    protected function configureFormFields(FormMapper $form)
2719
    {
2720
    }
2721
2722
    protected function configureListFields(ListMapper $list)
2723
    {
2724
    }
2725
2726
    protected function configureDatagridFilters(DatagridMapper $filter)
2727
    {
2728
    }
2729
2730
    protected function configureShowFields(ShowMapper $show)
2731
    {
2732
    }
2733
2734
    protected function configureRoutes(RouteCollection $collection)
2735
    {
2736
    }
2737
2738
    /**
2739
     * Allows you to customize batch actions.
2740
     *
2741
     * @param array $actions List of actions
2742
     *
2743
     * @return array
2744
     */
2745
    protected function configureBatchActions($actions)
2746
    {
2747
        return $actions;
2748
    }
2749
2750
    /**
2751
     * NEXT_MAJOR: remove this method.
2752
     *
2753
     * @return mixed
2754
     *
2755
     * @deprecated Use configureTabMenu instead
2756
     */
2757
    protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
0 ignored issues
show
Unused Code introduced by
The parameter $menu 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...
Unused Code introduced by
The parameter $action 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...
Unused Code introduced by
The parameter $childAdmin 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...
2758
    {
2759
    }
2760
2761
    /**
2762
     * Configures the tab menu in your admin.
2763
     *
2764
     * @param string $action
2765
     *
2766
     * @return mixed
2767
     */
2768
    protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
2769
    {
2770
        // Use configureSideMenu not to mess with previous overrides
2771
        // TODO remove once deprecation period is over
2772
        $this->configureSideMenu($menu, $action, $childAdmin);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...in::configureSideMenu() has been deprecated with message: Use configureTabMenu instead

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

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

Loading history...
2773
    }
2774
2775
    /**
2776
     * build the view FieldDescription array.
2777
     */
2778
    protected function buildShow()
2779
    {
2780
        if ($this->show) {
2781
            return;
2782
        }
2783
2784
        $this->show = new FieldDescriptionCollection();
2785
        $mapper = new ShowMapper($this->showBuilder, $this->show, $this);
2786
2787
        $this->configureShowFields($mapper);
2788
2789
        foreach ($this->getExtensions() as $extension) {
2790
            $extension->configureShowFields($mapper);
2791
        }
2792
    }
2793
2794
    /**
2795
     * build the list FieldDescription array.
2796
     */
2797
    protected function buildList()
2798
    {
2799
        if ($this->list) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->list of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2800
            return;
2801
        }
2802
2803
        $this->list = $this->getListBuilder()->getBaseList();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getListBuilder()->getBaseList() of type object<Sonata\AdminBundl...dDescriptionCollection> is incompatible with the declared type array of property $list.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2804
2805
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
2806
2807
        if (count($this->getBatchActions()) > 0) {
2808
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2809
                $this->getClass(),
2810
                'batch',
2811
                [
2812
                    'label' => 'batch',
2813
                    'code' => '_batch',
2814
                    'sortable' => false,
2815
                    'virtual_field' => true,
2816
                ]
2817
            );
2818
2819
            $fieldDescription->setAdmin($this);
2820
            $fieldDescription->setTemplate($this->getTemplate('batch'));
2821
2822
            $mapper->add($fieldDescription, 'batch');
0 ignored issues
show
Documentation introduced by
$fieldDescription is of type object<Sonata\AdminBundl...ldDescriptionInterface>, 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...
2823
        }
2824
2825
        $this->configureListFields($mapper);
2826
2827
        foreach ($this->getExtensions() as $extension) {
2828
            $extension->configureListFields($mapper);
2829
        }
2830
2831
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
2832
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2833
                $this->getClass(),
2834
                'select',
2835
                [
2836
                    'label' => false,
2837
                    'code' => '_select',
2838
                    'sortable' => false,
2839
                    'virtual_field' => false,
2840
                ]
2841
            );
2842
2843
            $fieldDescription->setAdmin($this);
2844
            $fieldDescription->setTemplate($this->getTemplate('select'));
2845
2846
            $mapper->add($fieldDescription, 'select');
0 ignored issues
show
Documentation introduced by
$fieldDescription is of type object<Sonata\AdminBundl...ldDescriptionInterface>, 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...
2847
        }
2848
    }
2849
2850
    /**
2851
     * Build the form FieldDescription collection.
2852
     */
2853
    protected function buildForm()
2854
    {
2855
        if ($this->form) {
2856
            return;
2857
        }
2858
2859
        // append parent object if any
2860
        // todo : clean the way the Admin class can retrieve set the object
2861
        if ($this->isChild() && $this->getParentAssociationMapping()) {
2862
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
2863
2864
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
2865
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
2866
2867
            $object = $this->getSubject();
2868
2869
            $value = $propertyAccessor->getValue($object, $propertyPath);
2870
2871
            if (is_array($value) || ($value instanceof \Traversable && $value instanceof \ArrayAccess)) {
2872
                $value[] = $parent;
2873
                $propertyAccessor->setValue($object, $propertyPath, $value);
2874
            } else {
2875
                $propertyAccessor->setValue($object, $propertyPath, $parent);
2876
            }
2877
        }
2878
2879
        $formBuilder = $this->getFormBuilder();
2880
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
2881
            $this->preValidate($event->getData());
2882
        }, 100);
2883
2884
        $this->form = $formBuilder->getForm();
2885
    }
2886
2887
    /**
2888
     * Gets the subclass corresponding to the given name.
2889
     *
2890
     * @param string $name The name of the sub class
2891
     *
2892
     * @return string the subclass
2893
     */
2894
    protected function getSubClass($name)
2895
    {
2896
        if ($this->hasSubClass($name)) {
2897
            return $this->subClasses[$name];
2898
        }
2899
2900
        throw new \RuntimeException(sprintf(
2901
            'Unable to find the subclass `%s` for admin `%s`',
2902
            $name,
2903
            get_class($this)
2904
        ));
2905
    }
2906
2907
    /**
2908
     * Attach the inline validator to the model metadata, this must be done once per admin.
2909
     */
2910
    protected function attachInlineValidator()
2911
    {
2912
        $admin = $this;
2913
2914
        // add the custom inline validation option
2915
        $metadata = $this->validator->getMetadataFor($this->getClass());
2916
2917
        $metadata->addConstraint(new InlineConstraint([
2918
            'service' => $this,
2919
            'method' => function (ErrorElement $errorElement, $object) use ($admin) {
2920
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
2921
2922
                // This avoid the main validation to be cascaded to children
2923
                // The problem occurs when a model Page has a collection of Page as property
2924
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
2925
                    return;
2926
                }
2927
2928
                $admin->validate($errorElement, $object);
2929
2930
                foreach ($admin->getExtensions() as $extension) {
2931
                    $extension->validate($admin, $errorElement, $object);
2932
                }
2933
            },
2934
            'serializingWarning' => true,
2935
        ]));
2936
    }
2937
2938
    /**
2939
     * Predefine per page options.
2940
     */
2941
    protected function predefinePerPageOptions()
2942
    {
2943
        array_unshift($this->perPageOptions, $this->maxPerPage);
2944
        $this->perPageOptions = array_unique($this->perPageOptions);
2945
        sort($this->perPageOptions);
2946
    }
2947
2948
    /**
2949
     * Return list routes with permissions name.
2950
     *
2951
     * @return array
2952
     */
2953
    protected function getAccess()
2954
    {
2955
        $access = array_merge([
2956
            'acl' => 'MASTER',
2957
            'export' => 'EXPORT',
2958
            'historyCompareRevisions' => 'EDIT',
2959
            'historyViewRevision' => 'EDIT',
2960
            'history' => 'EDIT',
2961
            'edit' => 'EDIT',
2962
            'show' => 'VIEW',
2963
            'create' => 'CREATE',
2964
            'delete' => 'DELETE',
2965
            'batchDelete' => 'DELETE',
2966
            'list' => 'LIST',
2967
        ], $this->getAccessMapping());
2968
2969
        foreach ($this->extensions as $extension) {
2970
            // TODO: remove method check in next major release
2971
            if (method_exists($extension, 'getAccessMapping')) {
2972
                $access = array_merge($access, $extension->getAccessMapping($this));
2973
            }
2974
        }
2975
2976
        return $access;
2977
    }
2978
2979
    /**
2980
     * Returns a list of default filters.
2981
     */
2982
    protected function configureDefaultFilterValues(array &$filterValues)
2983
    {
2984
    }
2985
2986
    /**
2987
     * Build all the related urls to the current admin.
2988
     */
2989
    private function buildRoutes()
2990
    {
2991
        if ($this->loaded['routes']) {
2992
            return;
2993
        }
2994
2995
        $this->loaded['routes'] = true;
2996
2997
        $this->routes = new RouteCollection(
2998
            $this->getBaseCodeRoute(),
2999
            $this->getBaseRouteName(),
3000
            $this->getBaseRoutePattern(),
3001
            $this->getBaseControllerName()
3002
        );
3003
3004
        $this->routeBuilder->build($this, $this->routes);
3005
3006
        $this->configureRoutes($this->routes);
3007
3008
        foreach ($this->getExtensions() as $extension) {
3009
            $extension->configureRoutes($this, $this->routes);
3010
        }
3011
    }
3012
}
3013