Completed
Push — 3.x ( d77cd9...d0d9b3 )
by Grégoire
04:16
created

AbstractAdmin::addParentAssociationMapping()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
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\Templating\MutableTemplateRegistryInterface;
38
use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface;
39
use Sonata\CoreBundle\Model\Metadata;
40
use Sonata\CoreBundle\Validator\Constraints\InlineConstraint;
41
use Sonata\CoreBundle\Validator\ErrorElement;
42
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
43
use Symfony\Component\Form\Form;
44
use Symfony\Component\Form\FormBuilderInterface;
45
use Symfony\Component\Form\FormEvent;
46
use Symfony\Component\Form\FormEvents;
47
use Symfony\Component\HttpFoundation\Request;
48
use Symfony\Component\PropertyAccess\PropertyPath;
49
use Symfony\Component\Routing\Generator\UrlGeneratorInterface as RoutingUrlGeneratorInterface;
50
use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
51
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
52
use Symfony\Component\Translation\TranslatorInterface;
53
use Symfony\Component\Validator\Validator\ValidatorInterface;
54
55
/**
56
 * @author Thomas Rabaix <[email protected]>
57
 */
58
abstract class AbstractAdmin implements AdminInterface, DomainObjectInterface, AdminTreeInterface
59
{
60
    const CONTEXT_MENU = 'menu';
61
    const CONTEXT_DASHBOARD = 'dashboard';
62
63
    const CLASS_REGEX =
64
        '@
65
        (?:([A-Za-z0-9]*)\\\)?        # vendor name / app name
66
        (Bundle\\\)?                  # optional bundle directory
67
        ([A-Za-z0-9]+?)(?:Bundle)?\\\ # bundle name, with optional suffix
68
        (
69
            Entity|Document|Model|PHPCR|CouchDocument|Phpcr|
70
            Doctrine\\\Orm|Doctrine\\\Phpcr|Doctrine\\\MongoDB|Doctrine\\\CouchDB
71
        )\\\(.*)@x';
72
73
    const MOSAIC_ICON_CLASS = 'fa fa-th-large fa-fw';
74
75
    /**
76
     * The list FieldDescription constructed from the configureListField method.
77
     *
78
     * @var array
79
     */
80
    protected $listFieldDescriptions = [];
81
82
    /**
83
     * The show FieldDescription constructed from the configureShowFields method.
84
     *
85
     * @var array
86
     */
87
    protected $showFieldDescriptions = [];
88
89
    /**
90
     * The list FieldDescription constructed from the configureFormField method.
91
     *
92
     * @var array
93
     */
94
    protected $formFieldDescriptions = [];
95
96
    /**
97
     * The filter FieldDescription constructed from the configureFilterField method.
98
     *
99
     * @var array
100
     */
101
    protected $filterFieldDescriptions = [];
102
103
    /**
104
     * The number of result to display in the list.
105
     *
106
     * @var int
107
     */
108
    protected $maxPerPage = 32;
109
110
    /**
111
     * The maximum number of page numbers to display in the list.
112
     *
113
     * @var int
114
     */
115
    protected $maxPageLinks = 25;
116
117
    /**
118
     * The base route name used to generate the routing information.
119
     *
120
     * @var string
121
     */
122
    protected $baseRouteName;
123
124
    /**
125
     * The base route pattern used to generate the routing information.
126
     *
127
     * @var string
128
     */
129
    protected $baseRoutePattern;
130
131
    /**
132
     * The base name controller used to generate the routing information.
133
     *
134
     * @var string
135
     */
136
    protected $baseControllerName;
137
138
    /**
139
     * The label class name  (used in the title/breadcrumb ...).
140
     *
141
     * @var string
142
     */
143
    protected $classnameLabel;
144
145
    /**
146
     * The translation domain to be used to translate messages.
147
     *
148
     * @var string
149
     */
150
    protected $translationDomain = 'messages';
151
152
    /**
153
     * Options to set to the form (ie, validation_groups).
154
     *
155
     * @var array
156
     */
157
    protected $formOptions = [];
158
159
    /**
160
     * Default values to the datagrid.
161
     *
162
     * @var array
163
     */
164
    protected $datagridValues = [
165
        '_page' => 1,
166
        '_per_page' => 32,
167
    ];
168
169
    /**
170
     * Predefined per page options.
171
     *
172
     * @var array
173
     */
174
    protected $perPageOptions = [16, 32, 64, 128, 256];
175
176
    /**
177
     * Pager type.
178
     *
179
     * @var string
180
     */
181
    protected $pagerType = Pager::TYPE_DEFAULT;
182
183
    /**
184
     * The code related to the admin.
185
     *
186
     * @var string
187
     */
188
    protected $code;
189
190
    /**
191
     * The label.
192
     *
193
     * @var string
194
     */
195
    protected $label;
196
197
    /**
198
     * Whether or not to persist the filters in the session.
199
     *
200
     * NEXT_MAJOR: remove this property
201
     *
202
     * @var bool
203
     *
204
     * @deprecated since 3.34, to be removed in 4.0.
205
     */
206
    protected $persistFilters = false;
207
208
    /**
209
     * Array of routes related to this admin.
210
     *
211
     * @var RouteCollection
212
     */
213
    protected $routes;
214
215
    /**
216
     * The subject only set in edit/update/create mode.
217
     *
218
     * @var object
219
     */
220
    protected $subject;
221
222
    /**
223
     * Define a Collection of child admin, ie /admin/order/{id}/order-element/{childId}.
224
     *
225
     * @var array
226
     */
227
    protected $children = [];
228
229
    /**
230
     * Reference the parent collection.
231
     *
232
     * @var AdminInterface|null
233
     */
234
    protected $parent = null;
235
236
    /**
237
     * The base code route refer to the prefix used to generate the route name.
238
     *
239
     * NEXT_MAJOR: remove this attribute.
240
     *
241
     * @deprecated This attribute is deprecated since 3.24 and will be removed in 4.0
242
     *
243
     * @var string
244
     */
245
    protected $baseCodeRoute = '';
246
247
    /**
248
     * NEXT_MAJOR: should be default array and private.
249
     *
250
     * @var string|array
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
     * @deprecated since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
409
     */
410
    protected $templates = [];
411
412
    /**
413
     * @var AdminExtensionInterface[]
414
     */
415
    protected $extensions = [];
416
417
    /**
418
     * @var LabelTranslatorStrategyInterface
419
     */
420
    protected $labelTranslatorStrategy;
421
422
    /**
423
     * Setting to true will enable preview mode for
424
     * the entity and show a preview button in the
425
     * edit/create forms.
426
     *
427
     * @var bool
428
     */
429
    protected $supportsPreviewMode = false;
430
431
    /**
432
     * Roles and permissions per role.
433
     *
434
     * @var array 'role' => ['permission', 'permission']
435
     */
436
    protected $securityInformation = [];
437
438
    protected $cacheIsGranted = [];
439
440
    /**
441
     * Action list for the search result.
442
     *
443
     * @var string[]
444
     */
445
    protected $searchResultActions = ['edit', 'show'];
446
447
    protected $listModes = [
448
        'list' => [
449
            'class' => 'fa fa-list fa-fw',
450
        ],
451
        'mosaic' => [
452
            'class' => self::MOSAIC_ICON_CLASS,
453
        ],
454
    ];
455
456
    /**
457
     * The Access mapping.
458
     *
459
     * @var array [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
460
     */
461
    protected $accessMapping = [];
462
463
    /**
464
     * @var MutableTemplateRegistryInterface
465
     */
466
    private $templateRegistry;
467
468
    /**
469
     * The class name managed by the admin class.
470
     *
471
     * @var string
472
     */
473
    private $class;
474
475
    /**
476
     * The subclasses supported by the admin class.
477
     *
478
     * @var array
479
     */
480
    private $subClasses = [];
481
482
    /**
483
     * The list collection.
484
     *
485
     * @var array
486
     */
487
    private $list;
488
489
    /**
490
     * @var FieldDescriptionCollection
491
     */
492
    private $show;
493
494
    /**
495
     * @var Form
496
     */
497
    private $form;
498
499
    /**
500
     * @var DatagridInterface
501
     */
502
    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...
503
504
    /**
505
     * The cached base route name.
506
     *
507
     * @var string
508
     */
509
    private $cachedBaseRouteName;
510
511
    /**
512
     * The cached base route pattern.
513
     *
514
     * @var string
515
     */
516
    private $cachedBaseRoutePattern;
517
518
    /**
519
     * The form group disposition.
520
     *
521
     * @var array|bool
522
     */
523
    private $formGroups = false;
524
525
    /**
526
     * The form tabs disposition.
527
     *
528
     * @var array|bool
529
     */
530
    private $formTabs = false;
531
532
    /**
533
     * The view group disposition.
534
     *
535
     * @var array|bool
536
     */
537
    private $showGroups = false;
538
539
    /**
540
     * The view tab disposition.
541
     *
542
     * @var array|bool
543
     */
544
    private $showTabs = false;
545
546
    /**
547
     * The manager type to use for the admin.
548
     *
549
     * @var string
550
     */
551
    private $managerType;
552
553
    /**
554
     * The breadcrumbsBuilder component.
555
     *
556
     * @var BreadcrumbsBuilderInterface
557
     */
558
    private $breadcrumbsBuilder;
559
560
    /**
561
     * Component responsible for persisting filters.
562
     *
563
     * @var FilterPersisterInterface|null
564
     */
565
    private $filterPersister;
566
567
    /**
568
     * @param string $code
569
     * @param string $class
570
     * @param string $baseControllerName
571
     */
572
    public function __construct($code, $class, $baseControllerName)
573
    {
574
        $this->code = $code;
575
        $this->class = $class;
576
        $this->baseControllerName = $baseControllerName;
577
578
        $this->predefinePerPageOptions();
579
        $this->datagridValues['_per_page'] = $this->maxPerPage;
580
    }
581
582
    /**
583
     * {@inheritdoc}
584
     *
585
     * NEXT_MAJOR: return null to indicate no override
586
     */
587
    public function getExportFormats()
588
    {
589
        return [
590
            'json', 'xml', 'csv', 'xls',
591
        ];
592
    }
593
594
    /**
595
     * @return array
596
     */
597
    public function getExportFields()
598
    {
599
        $fields = $this->getModelManager()->getExportFields($this->getClass());
600
601
        foreach ($this->getExtensions() as $extension) {
602
            if (method_exists($extension, 'configureExportFields')) {
603
                $fields = $extension->configureExportFields($this, $fields);
604
            }
605
        }
606
607
        return $fields;
608
    }
609
610
    public function getDataSourceIterator()
611
    {
612
        $datagrid = $this->getDatagrid();
613
        $datagrid->buildPager();
614
615
        $fields = [];
616
617
        foreach ($this->getExportFields() as $key => $field) {
618
            $label = $this->getTranslationLabel($field, 'export', 'label');
619
            $transLabel = $this->trans($label);
620
621
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
622
            // No translation key exists
623
            if ($transLabel == $label) {
624
                $fields[$key] = $field;
625
            } else {
626
                $fields[$transLabel] = $field;
627
            }
628
        }
629
630
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
631
    }
632
633
    public function validate(ErrorElement $errorElement, $object)
634
    {
635
    }
636
637
    /**
638
     * define custom variable.
639
     */
640
    public function initialize()
641
    {
642
        if (!$this->classnameLabel) {
643
            $this->classnameLabel = substr($this->getClass(), strrpos($this->getClass(), '\\') + 1);
644
        }
645
646
        // NEXT_MAJOR: Remove this line.
647
        $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...
648
649
        $this->configure();
650
    }
651
652
    public function configure()
653
    {
654
    }
655
656
    public function update($object)
657
    {
658
        $this->preUpdate($object);
659
        foreach ($this->extensions as $extension) {
660
            $extension->preUpdate($this, $object);
661
        }
662
663
        $result = $this->getModelManager()->update($object);
664
        // BC compatibility
665
        if (null !== $result) {
666
            $object = $result;
667
        }
668
669
        $this->postUpdate($object);
670
        foreach ($this->extensions as $extension) {
671
            $extension->postUpdate($this, $object);
672
        }
673
674
        return $object;
675
    }
676
677
    public function create($object)
678
    {
679
        $this->prePersist($object);
680
        foreach ($this->extensions as $extension) {
681
            $extension->prePersist($this, $object);
682
        }
683
684
        $result = $this->getModelManager()->create($object);
685
        // BC compatibility
686
        if (null !== $result) {
687
            $object = $result;
688
        }
689
690
        $this->postPersist($object);
691
        foreach ($this->extensions as $extension) {
692
            $extension->postPersist($this, $object);
693
        }
694
695
        $this->createObjectSecurity($object);
696
697
        return $object;
698
    }
699
700
    public function delete($object)
701
    {
702
        $this->preRemove($object);
703
        foreach ($this->extensions as $extension) {
704
            $extension->preRemove($this, $object);
705
        }
706
707
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
708
        $this->getModelManager()->delete($object);
709
710
        $this->postRemove($object);
711
        foreach ($this->extensions as $extension) {
712
            $extension->postRemove($this, $object);
713
        }
714
    }
715
716
    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...
717
    {
718
    }
719
720
    public function preUpdate($object)
721
    {
722
    }
723
724
    public function postUpdate($object)
725
    {
726
    }
727
728
    public function prePersist($object)
729
    {
730
    }
731
732
    public function postPersist($object)
733
    {
734
    }
735
736
    public function preRemove($object)
737
    {
738
    }
739
740
    public function postRemove($object)
741
    {
742
    }
743
744
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
745
    {
746
    }
747
748
    public function getFilterParameters()
749
    {
750
        $parameters = [];
751
752
        // build the values array
753
        if ($this->hasRequest()) {
754
            $filters = $this->request->query->get('filter', []);
755
756
            // if filter persistence is configured
757
            // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
758
            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.34, 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...
759
                // if reset filters is asked, remove from storage
760
                if ('reset' === $this->request->query->get('filters')) {
761
                    $this->filterPersister->reset($this->getCode());
762
                }
763
764
                // if no filters, fetch from storage
765
                // otherwise save to storage
766
                if (empty($filters)) {
767
                    $filters = $this->filterPersister->get($this->getCode());
768
                } else {
769
                    $this->filterPersister->set($this->getCode(), $filters);
770
                }
771
            }
772
773
            $parameters = array_merge(
774
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
775
                $this->datagridValues,
776
                $this->getDefaultFilterValues(),
777
                $filters
778
            );
779
780
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
781
                $parameters['_per_page'] = $this->maxPerPage;
782
            }
783
784
            // always force the parent value
785
            if ($this->isChild() && $this->getParentAssociationMapping()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getParentAssociationMapping() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
786
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
787
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
788
            }
789
        }
790
791
        return $parameters;
792
    }
793
794
    public function buildDatagrid()
795
    {
796
        if ($this->datagrid) {
797
            return;
798
        }
799
800
        $filterParameters = $this->getFilterParameters();
801
802
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
803
        if (isset($filterParameters['_sort_by']) && is_string($filterParameters['_sort_by'])) {
804
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
805
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
806
            } else {
807
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
808
                    $this->getClass(),
809
                    $filterParameters['_sort_by'],
810
                    []
811
                );
812
813
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
814
            }
815
        }
816
817
        // initialize the datagrid
818
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
819
820
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
821
822
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
823
824
        // build the datagrid filter
825
        $this->configureDatagridFilters($mapper);
826
827
        // ok, try to limit to add parent filter
828
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getParentAssociationMapping() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
829
            $mapper->add($this->getParentAssociationMapping(), null, [
830
                'show_filter' => false,
831
                'label' => false,
832
                'field_type' => ModelHiddenType::class,
833
                'field_options' => [
834
                    'model_manager' => $this->getModelManager(),
835
                ],
836
                'operator_type' => HiddenType::class,
837
            ], null, null, [
838
                'admin_code' => $this->getParent()->getCode(),
839
            ]);
840
        }
841
842
        foreach ($this->getExtensions() as $extension) {
843
            $extension->configureDatagridFilters($mapper);
844
        }
845
    }
846
847
    /**
848
     * Returns the name of the parent related field, so the field can be use to set the default
849
     * value (ie the parent object) or to filter the object.
850
     *
851
     * @throws \InvalidArgumentException
852
     *
853
     * @return null|string
854
     */
855
    public function getParentAssociationMapping()
856
    {
857
        // NEXT_MAJOR: remove array check
858
        if (\is_array($this->parentAssociationMapping) && $this->getParent()) {
859
            $parent = $this->getParent()->getCode();
860
861
            if (array_key_exists($parent, $this->parentAssociationMapping)) {
862
                return $this->parentAssociationMapping[$parent];
863
            }
864
865
            throw new \InvalidArgumentException(sprintf(
866
                "There's no association between %s and %s.",
867
                $this->getCode(),
868
                $this->getParent()->getCode()
869
            ));
870
        }
871
872
        // NEXT_MAJOR: remove this line
873
        return $this->parentAssociationMapping;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->parentAssociationMapping; of type string|array adds the type array to the return on line 873 which is incompatible with the return type documented by Sonata\AdminBundle\Admin...arentAssociationMapping of type null|string.
Loading history...
874
    }
875
876
    /**
877
     * @param string $code
878
     * @param string $value
879
     */
880
    final public function addParentAssociationMapping($code, $value)
881
    {
882
        $this->parentAssociationMapping[$code] = $value;
883
    }
884
885
    /**
886
     * Returns the baseRoutePattern used to generate the routing information.
887
     *
888
     * @throws \RuntimeException
889
     *
890
     * @return string the baseRoutePattern used to generate the routing information
891
     */
892
    public function getBaseRoutePattern()
893
    {
894
        if (null !== $this->cachedBaseRoutePattern) {
895
            return $this->cachedBaseRoutePattern;
896
        }
897
898
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
899
            if (!$this->baseRoutePattern) {
900
                preg_match(self::CLASS_REGEX, $this->class, $matches);
901
902
                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...
903
                    throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
904
                }
905
            }
906
907
            $this->cachedBaseRoutePattern = sprintf('%s/%s/%s',
908
                $this->getParent()->getBaseRoutePattern(),
909
                $this->getParent()->getRouterIdParameter(),
910
                $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...
911
            );
912
        } elseif ($this->baseRoutePattern) {
913
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
914
        } else {
915
            preg_match(self::CLASS_REGEX, $this->class, $matches);
916
917
            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...
918
                throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
919
            }
920
921
            $this->cachedBaseRoutePattern = sprintf('/%s%s/%s',
922
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
923
                $this->urlize($matches[3], '-'),
924
                $this->urlize($matches[5], '-')
925
            );
926
        }
927
928
        return $this->cachedBaseRoutePattern;
929
    }
930
931
    /**
932
     * Returns the baseRouteName used to generate the routing information.
933
     *
934
     * @throws \RuntimeException
935
     *
936
     * @return string the baseRouteName used to generate the routing information
937
     */
938
    public function getBaseRouteName()
939
    {
940
        if (null !== $this->cachedBaseRouteName) {
941
            return $this->cachedBaseRouteName;
942
        }
943
944
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
945
            if (!$this->baseRouteName) {
946
                preg_match(self::CLASS_REGEX, $this->class, $matches);
947
948
                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...
949
                    throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
950
                }
951
            }
952
953
            $this->cachedBaseRouteName = sprintf('%s_%s',
954
                $this->getParent()->getBaseRouteName(),
955
                $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...
956
            );
957
        } elseif ($this->baseRouteName) {
958
            $this->cachedBaseRouteName = $this->baseRouteName;
959
        } else {
960
            preg_match(self::CLASS_REGEX, $this->class, $matches);
961
962
            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...
963
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
964
            }
965
966
            $this->cachedBaseRouteName = sprintf('admin_%s%s_%s',
967
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
968
                $this->urlize($matches[3]),
969
                $this->urlize($matches[5])
970
            );
971
        }
972
973
        return $this->cachedBaseRouteName;
974
    }
975
976
    /**
977
     * urlize the given word.
978
     *
979
     * @param string $word
980
     * @param string $sep  the separator
981
     *
982
     * @return string
983
     */
984
    public function urlize($word, $sep = '_')
985
    {
986
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
987
    }
988
989
    public function getClass()
990
    {
991
        if ($this->hasActiveSubClass()) {
992
            if ($this->getParentFieldDescription()) {
993
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
994
            }
995
996
            $subClass = $this->getRequest()->query->get('subclass');
997
998
            if (!$this->hasSubClass($subClass)) {
999
                throw new \RuntimeException(sprintf('Subclass "%" is not defined.', $subClass));
1000
            }
1001
1002
            return $this->getSubClass($subClass);
1003
        }
1004
1005
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
1006
        if ($this->subject && is_object($this->subject)) {
1007
            return ClassUtils::getClass($this->subject);
1008
        }
1009
1010
        return $this->class;
1011
    }
1012
1013
    public function getSubClasses()
1014
    {
1015
        return $this->subClasses;
1016
    }
1017
1018
    /**
1019
     * NEXT_MAJOR: remove this method.
1020
     */
1021
    public function addSubClass($subClass)
1022
    {
1023
        @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...
1024
            'Method "%s" is deprecated since 3.30 and will be removed in 4.0.',
1025
            __METHOD__
1026
        ), E_USER_DEPRECATED);
1027
1028
        if (!in_array($subClass, $this->subClasses)) {
1029
            $this->subClasses[] = $subClass;
1030
        }
1031
    }
1032
1033
    public function setSubClasses(array $subClasses)
1034
    {
1035
        $this->subClasses = $subClasses;
1036
    }
1037
1038
    public function hasSubClass($name)
1039
    {
1040
        return isset($this->subClasses[$name]);
1041
    }
1042
1043
    public function hasActiveSubClass()
1044
    {
1045
        if (count($this->subClasses) > 0 && $this->request) {
1046
            return null !== $this->getRequest()->query->get('subclass');
1047
        }
1048
1049
        return false;
1050
    }
1051
1052
    public function getActiveSubClass()
1053
    {
1054
        if (!$this->hasActiveSubClass()) {
1055
            return;
1056
        }
1057
1058
        return $this->getSubClass($this->getActiveSubclassCode());
1059
    }
1060
1061
    public function getActiveSubclassCode()
1062
    {
1063
        if (!$this->hasActiveSubClass()) {
1064
            return;
1065
        }
1066
1067
        $subClass = $this->getRequest()->query->get('subclass');
1068
1069
        if (!$this->hasSubClass($subClass)) {
1070
            return;
1071
        }
1072
1073
        return $subClass;
1074
    }
1075
1076
    public function getBatchActions()
1077
    {
1078
        $actions = [];
1079
1080
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1081
            $actions['delete'] = [
1082
                'label' => 'action_delete',
1083
                'translation_domain' => 'SonataAdminBundle',
1084
                'ask_confirmation' => true, // by default always true
1085
            ];
1086
        }
1087
1088
        $actions = $this->configureBatchActions($actions);
1089
1090
        foreach ($this->getExtensions() as $extension) {
1091
            // TODO: remove method check in next major release
1092
            if (method_exists($extension, 'configureBatchActions')) {
1093
                $actions = $extension->configureBatchActions($this, $actions);
1094
            }
1095
        }
1096
1097
        foreach ($actions  as $name => &$action) {
1098
            if (!array_key_exists('label', $action)) {
1099
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1100
            }
1101
1102
            if (!array_key_exists('translation_domain', $action)) {
1103
                $action['translation_domain'] = $this->getTranslationDomain();
1104
            }
1105
        }
1106
1107
        return $actions;
1108
    }
1109
1110
    public function getRoutes()
1111
    {
1112
        $this->buildRoutes();
1113
1114
        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...
1115
    }
1116
1117
    public function getRouterIdParameter()
1118
    {
1119
        return '{'.$this->getIdParameter().'}';
1120
    }
1121
1122
    public function getIdParameter()
1123
    {
1124
        $parameter = 'id';
1125
1126
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1127
            $parameter = 'child'.ucfirst($parameter);
1128
        }
1129
1130
        return $parameter;
1131
    }
1132
1133
    public function hasRoute($name)
1134
    {
1135
        if (!$this->routeGenerator) {
1136
            throw new \RuntimeException('RouteGenerator cannot be null');
1137
        }
1138
1139
        return $this->routeGenerator->hasAdminRoute($this, $name);
1140
    }
1141
1142
    public function isCurrentRoute($name, $adminCode = null)
1143
    {
1144
        if (!$this->hasRequest()) {
1145
            return false;
1146
        }
1147
1148
        $request = $this->getRequest();
1149
        $route = $request->get('_route');
1150
1151
        if ($adminCode) {
1152
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1153
        } else {
1154
            $admin = $this;
1155
        }
1156
1157
        if (!$admin) {
1158
            return false;
1159
        }
1160
1161
        return ($admin->getBaseRouteName().'_'.$name) == $route;
1162
    }
1163
1164
    public function generateObjectUrl($name, $object, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1165
    {
1166
        $parameters['id'] = $this->getUrlsafeIdentifier($object);
1167
1168
        return $this->generateUrl($name, $parameters, $absolute);
1169
    }
1170
1171
    public function generateUrl($name, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1172
    {
1173
        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...
1174
    }
1175
1176
    public function generateMenuUrl($name, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1177
    {
1178
        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...
1179
    }
1180
1181
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry)
1182
    {
1183
        $this->templateRegistry = $templateRegistry;
1184
    }
1185
1186
    public function setTemplates(array $templates)
1187
    {
1188
        // NEXT_MAJOR: Remove this line
1189
        $this->templates = $templates;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin\AbstractAdmin::$templates has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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...
1190
1191
        $this->getTemplateRegistry()->setTemplates($templates);
1192
    }
1193
1194
    /**
1195
     * @param string $name
1196
     * @param string $template
1197
     */
1198
    public function setTemplate($name, $template)
1199
    {
1200
        // NEXT_MAJOR: Remove this line
1201
        $this->templates[$name] = $template;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin\AbstractAdmin::$templates has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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...
1202
1203
        $this->getTemplateRegistry()->setTemplate($name, $template);
1204
    }
1205
1206
    /**
1207
     * @deprecated since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1208
     *
1209
     * @return array
1210
     */
1211
    public function getTemplates()
1212
    {
1213
        return $this->getTemplateRegistry()->getTemplates();
1214
    }
1215
1216
    /**
1217
     * @deprecated since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1218
     *
1219
     * @param string $name
1220
     *
1221
     * @return null|string
1222
     */
1223
    public function getTemplate($name)
1224
    {
1225
        return $this->getTemplateRegistry()->getTemplate($name);
1226
    }
1227
1228
    public function getNewInstance()
1229
    {
1230
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1231
        foreach ($this->getExtensions() as $extension) {
1232
            $extension->alterNewInstance($this, $object);
1233
        }
1234
1235
        return $object;
1236
    }
1237
1238
    public function getFormBuilder()
1239
    {
1240
        $this->formOptions['data_class'] = $this->getClass();
1241
1242
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1243
            $this->getUniqid(),
1244
            $this->formOptions
1245
        );
1246
1247
        $this->defineFormBuilder($formBuilder);
1248
1249
        return $formBuilder;
1250
    }
1251
1252
    /**
1253
     * This method is being called by the main admin class and the child class,
1254
     * the getFormBuilder is only call by the main admin class.
1255
     */
1256
    public function defineFormBuilder(FormBuilderInterface $formBuilder)
1257
    {
1258
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1259
1260
        $this->configureFormFields($mapper);
1261
1262
        foreach ($this->getExtensions() as $extension) {
1263
            $extension->configureFormFields($mapper);
1264
        }
1265
1266
        $this->attachInlineValidator();
1267
    }
1268
1269
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
1270
    {
1271
        $pool = $this->getConfigurationPool();
1272
1273
        $adminCode = $fieldDescription->getOption('admin_code');
1274
1275
        if (null !== $adminCode) {
1276
            $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...
1277
        } else {
1278
            $admin = $pool->getAdminByClass($fieldDescription->getTargetEntity());
1279
        }
1280
1281
        if (!$admin) {
1282
            return;
1283
        }
1284
1285
        if ($this->hasRequest()) {
1286
            $admin->setRequest($this->getRequest());
1287
        }
1288
1289
        $fieldDescription->setAssociationAdmin($admin);
1290
    }
1291
1292
    public function getObject($id)
1293
    {
1294
        $object = $this->getModelManager()->find($this->getClass(), $id);
1295
        foreach ($this->getExtensions() as $extension) {
1296
            $extension->alterObject($this, $object);
1297
        }
1298
1299
        return $object;
1300
    }
1301
1302
    public function getForm()
1303
    {
1304
        $this->buildForm();
1305
1306
        return $this->form;
1307
    }
1308
1309
    public function getList()
1310
    {
1311
        $this->buildList();
1312
1313
        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...
1314
    }
1315
1316
    public function createQuery($context = 'list')
1317
    {
1318
        if (func_num_args() > 0) {
1319
            @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...
1320
                'The $context argument of '.__METHOD__.' is deprecated since 3.3, to be removed in 4.0.',
1321
                E_USER_DEPRECATED
1322
            );
1323
        }
1324
        $query = $this->getModelManager()->createQuery($this->getClass());
1325
1326
        foreach ($this->extensions as $extension) {
1327
            $extension->configureQuery($this, $query, $context);
1328
        }
1329
1330
        return $query;
1331
    }
1332
1333
    public function getDatagrid()
1334
    {
1335
        $this->buildDatagrid();
1336
1337
        return $this->datagrid;
1338
    }
1339
1340
    public function buildTabMenu($action, AdminInterface $childAdmin = null)
1341
    {
1342
        if ($this->loaded['tab_menu']) {
1343
            return;
1344
        }
1345
1346
        $this->loaded['tab_menu'] = true;
1347
1348
        $menu = $this->menuFactory->createItem('root');
1349
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1350
        $menu->setExtra('translation_domain', $this->translationDomain);
1351
1352
        // Prevents BC break with KnpMenuBundle v1.x
1353
        if (method_exists($menu, 'setCurrentUri')) {
1354
            $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...
1355
        }
1356
1357
        $this->configureTabMenu($menu, $action, $childAdmin);
1358
1359
        foreach ($this->getExtensions() as $extension) {
1360
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1361
        }
1362
1363
        $this->menu = $menu;
1364
    }
1365
1366
    public function buildSideMenu($action, AdminInterface $childAdmin = null)
1367
    {
1368
        return $this->buildTabMenu($action, $childAdmin);
1369
    }
1370
1371
    /**
1372
     * @param string $action
1373
     *
1374
     * @return ItemInterface
1375
     */
1376
    public function getSideMenu($action, AdminInterface $childAdmin = null)
1377
    {
1378
        if ($this->isChild()) {
1379
            return $this->getParent()->getSideMenu($action, $this);
1380
        }
1381
1382
        $this->buildSideMenu($action, $childAdmin);
1383
1384
        return $this->menu;
1385
    }
1386
1387
    /**
1388
     * Returns the root code.
1389
     *
1390
     * @return string the root code
1391
     */
1392
    public function getRootCode()
1393
    {
1394
        return $this->getRoot()->getCode();
1395
    }
1396
1397
    /**
1398
     * Returns the master admin.
1399
     *
1400
     * @return AbstractAdmin the root admin class
1401
     */
1402
    public function getRoot()
1403
    {
1404
        $parentFieldDescription = $this->getParentFieldDescription();
1405
1406
        if (!$parentFieldDescription) {
1407
            return $this;
1408
        }
1409
1410
        return $parentFieldDescription->getAdmin()->getRoot();
1411
    }
1412
1413
    public function setBaseControllerName($baseControllerName)
1414
    {
1415
        $this->baseControllerName = $baseControllerName;
1416
    }
1417
1418
    public function getBaseControllerName()
1419
    {
1420
        return $this->baseControllerName;
1421
    }
1422
1423
    /**
1424
     * @param string $label
1425
     */
1426
    public function setLabel($label)
1427
    {
1428
        $this->label = $label;
1429
    }
1430
1431
    public function getLabel()
1432
    {
1433
        return $this->label;
1434
    }
1435
1436
    /**
1437
     * @param bool $persist
1438
     *
1439
     * NEXT_MAJOR: remove this method
1440
     *
1441
     * @deprecated since 3.34, to be removed in 4.0.
1442
     */
1443
    public function setPersistFilters($persist)
1444
    {
1445
        @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...
1446
            'The '.__METHOD__.' method is deprecated since version 3.34 and will be removed in 4.0.',
1447
            E_USER_DEPRECATED
1448
        );
1449
1450
        $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.34, 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...
1451
    }
1452
1453
    /**
1454
     * @param FilterPersisterInterface|null $filterPersister
1455
     */
1456
    public function setFilterPersister(FilterPersisterInterface $filterPersister = null)
1457
    {
1458
        $this->filterPersister = $filterPersister;
1459
        // NEXT_MAJOR remove the deprecated property will be removed. Needed for persisted filter condition.
1460
        $this->persistFilters = true;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$persistFilters has been deprecated with message: since 3.34, 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...
1461
    }
1462
1463
    /**
1464
     * @param int $maxPerPage
1465
     */
1466
    public function setMaxPerPage($maxPerPage)
1467
    {
1468
        $this->maxPerPage = $maxPerPage;
1469
    }
1470
1471
    /**
1472
     * @return int
1473
     */
1474
    public function getMaxPerPage()
1475
    {
1476
        return $this->maxPerPage;
1477
    }
1478
1479
    /**
1480
     * @param int $maxPageLinks
1481
     */
1482
    public function setMaxPageLinks($maxPageLinks)
1483
    {
1484
        $this->maxPageLinks = $maxPageLinks;
1485
    }
1486
1487
    /**
1488
     * @return int
1489
     */
1490
    public function getMaxPageLinks()
1491
    {
1492
        return $this->maxPageLinks;
1493
    }
1494
1495
    public function getFormGroups()
1496
    {
1497
        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 1497 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getFormGroups of type array.
Loading history...
1498
    }
1499
1500
    public function setFormGroups(array $formGroups)
1501
    {
1502
        $this->formGroups = $formGroups;
1503
    }
1504
1505
    public function removeFieldFromFormGroup($key)
1506
    {
1507
        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...
1508
            unset($this->formGroups[$name]['fields'][$key]);
1509
1510
            if (empty($this->formGroups[$name]['fields'])) {
1511
                unset($this->formGroups[$name]);
1512
            }
1513
        }
1514
    }
1515
1516
    /**
1517
     * @param array $group
1518
     */
1519
    public function reorderFormGroup($group, array $keys)
1520
    {
1521
        $formGroups = $this->getFormGroups();
1522
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1523
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups() on line 1521 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...
1524
    }
1525
1526
    public function getFormTabs()
1527
    {
1528
        return $this->formTabs;
1529
    }
1530
1531
    public function setFormTabs(array $formTabs)
1532
    {
1533
        $this->formTabs = $formTabs;
1534
    }
1535
1536
    public function getShowTabs()
1537
    {
1538
        return $this->showTabs;
1539
    }
1540
1541
    public function setShowTabs(array $showTabs)
1542
    {
1543
        $this->showTabs = $showTabs;
1544
    }
1545
1546
    public function getShowGroups()
1547
    {
1548
        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 1548 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getShowGroups of type array.
Loading history...
1549
    }
1550
1551
    public function setShowGroups(array $showGroups)
1552
    {
1553
        $this->showGroups = $showGroups;
1554
    }
1555
1556
    public function reorderShowGroup($group, array $keys)
1557
    {
1558
        $showGroups = $this->getShowGroups();
1559
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1560
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups() on line 1558 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...
1561
    }
1562
1563
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
1564
    {
1565
        $this->parentFieldDescription = $parentFieldDescription;
1566
    }
1567
1568
    public function getParentFieldDescription()
1569
    {
1570
        return $this->parentFieldDescription;
1571
    }
1572
1573
    public function hasParentFieldDescription()
1574
    {
1575
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1576
    }
1577
1578
    public function setSubject($subject)
1579
    {
1580
        if (is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1581
            $message = <<<'EOT'
1582
You are trying to set entity an instance of "%s",
1583
which is not the one registered with this admin class ("%s").
1584
This is deprecated since 3.5 and will no longer be supported in 4.0.
1585
EOT;
1586
1587
            @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...
1588
                sprintf($message, get_class($subject), $this->getClass()),
1589
                E_USER_DEPRECATED
1590
            ); // NEXT_MAJOR : throw an exception instead
1591
        }
1592
1593
        $this->subject = $subject;
1594
    }
1595
1596
    public function getSubject()
1597
    {
1598
        if (null === $this->subject && $this->request && !$this->hasParentFieldDescription()) {
1599
            $id = $this->request->get($this->getIdParameter());
1600
1601
            if (null !== $id) {
1602
                $this->subject = $this->getObject($id);
1603
            }
1604
        }
1605
1606
        return $this->subject;
1607
    }
1608
1609
    public function hasSubject()
1610
    {
1611
        return (bool) $this->getSubject();
1612
    }
1613
1614
    public function getFormFieldDescriptions()
1615
    {
1616
        $this->buildForm();
1617
1618
        return $this->formFieldDescriptions;
1619
    }
1620
1621
    public function getFormFieldDescription($name)
1622
    {
1623
        return $this->hasFormFieldDescription($name) ? $this->formFieldDescriptions[$name] : null;
1624
    }
1625
1626
    /**
1627
     * Returns true if the admin has a FieldDescription with the given $name.
1628
     *
1629
     * @param string $name
1630
     *
1631
     * @return bool
1632
     */
1633
    public function hasFormFieldDescription($name)
1634
    {
1635
        return array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1636
    }
1637
1638
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1639
    {
1640
        $this->formFieldDescriptions[$name] = $fieldDescription;
1641
    }
1642
1643
    /**
1644
     * remove a FieldDescription.
1645
     *
1646
     * @param string $name
1647
     */
1648
    public function removeFormFieldDescription($name)
1649
    {
1650
        unset($this->formFieldDescriptions[$name]);
1651
    }
1652
1653
    /**
1654
     * build and return the collection of form FieldDescription.
1655
     *
1656
     * @return array collection of form FieldDescription
1657
     */
1658
    public function getShowFieldDescriptions()
1659
    {
1660
        $this->buildShow();
1661
1662
        return $this->showFieldDescriptions;
1663
    }
1664
1665
    /**
1666
     * Returns the form FieldDescription with the given $name.
1667
     *
1668
     * @param string $name
1669
     *
1670
     * @return FieldDescriptionInterface
1671
     */
1672
    public function getShowFieldDescription($name)
1673
    {
1674
        $this->buildShow();
1675
1676
        return $this->hasShowFieldDescription($name) ? $this->showFieldDescriptions[$name] : null;
1677
    }
1678
1679
    public function hasShowFieldDescription($name)
1680
    {
1681
        return array_key_exists($name, $this->showFieldDescriptions);
1682
    }
1683
1684
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1685
    {
1686
        $this->showFieldDescriptions[$name] = $fieldDescription;
1687
    }
1688
1689
    public function removeShowFieldDescription($name)
1690
    {
1691
        unset($this->showFieldDescriptions[$name]);
1692
    }
1693
1694
    public function getListFieldDescriptions()
1695
    {
1696
        $this->buildList();
1697
1698
        return $this->listFieldDescriptions;
1699
    }
1700
1701
    public function getListFieldDescription($name)
1702
    {
1703
        return $this->hasListFieldDescription($name) ? $this->listFieldDescriptions[$name] : null;
1704
    }
1705
1706
    public function hasListFieldDescription($name)
1707
    {
1708
        $this->buildList();
1709
1710
        return array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1711
    }
1712
1713
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1714
    {
1715
        $this->listFieldDescriptions[$name] = $fieldDescription;
1716
    }
1717
1718
    public function removeListFieldDescription($name)
1719
    {
1720
        unset($this->listFieldDescriptions[$name]);
1721
    }
1722
1723
    public function getFilterFieldDescription($name)
1724
    {
1725
        return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null;
1726
    }
1727
1728
    public function hasFilterFieldDescription($name)
1729
    {
1730
        return array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1731
    }
1732
1733
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1734
    {
1735
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1736
    }
1737
1738
    public function removeFilterFieldDescription($name)
1739
    {
1740
        unset($this->filterFieldDescriptions[$name]);
1741
    }
1742
1743
    public function getFilterFieldDescriptions()
1744
    {
1745
        $this->buildDatagrid();
1746
1747
        return $this->filterFieldDescriptions;
1748
    }
1749
1750
    public function addChild(AdminInterface $child)
1751
    {
1752
        for ($parentAdmin = $this; null !== $parentAdmin; $parentAdmin = $parentAdmin->getParent()) {
1753
            if ($parentAdmin->getCode() !== $child->getCode()) {
1754
                continue;
1755
            }
1756
1757
            throw new \RuntimeException(sprintf(
1758
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1759
                $child->getCode(), $this->getCode()
1760
            ));
1761
        }
1762
1763
        $this->children[$child->getCode()] = $child;
1764
1765
        $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...
1766
1767
        // NEXT_MAJOR: remove $args and add $field parameter to this function on next Major
1768
1769
        $args = \func_get_args();
1770
1771
        if (isset($args[1])) {
1772
            $child->addParentAssociationMapping($this->getCode(), $args[1]);
1773
        } else {
1774
            @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...
1775
                'Calling "addChild" without second argument is deprecated since 3.x'
1776
                .' and will not be allowed in 4.0.',
1777
                E_USER_DEPRECATED
1778
            );
1779
        }
1780
    }
1781
1782
    public function hasChild($code)
1783
    {
1784
        return isset($this->children[$code]);
1785
    }
1786
1787
    public function getChildren()
1788
    {
1789
        return $this->children;
1790
    }
1791
1792
    public function getChild($code)
1793
    {
1794
        return $this->hasChild($code) ? $this->children[$code] : null;
1795
    }
1796
1797
    public function setParent(AdminInterface $parent)
1798
    {
1799
        $this->parent = $parent;
1800
    }
1801
1802
    public function getParent()
1803
    {
1804
        return $this->parent;
1805
    }
1806
1807
    final public function getRootAncestor()
1808
    {
1809
        $parent = $this;
1810
1811
        while ($parent->isChild()) {
1812
            $parent = $parent->getParent();
1813
        }
1814
1815
        return $parent;
1816
    }
1817
1818
    final public function getChildDepth()
1819
    {
1820
        $parent = $this;
1821
        $depth = 0;
1822
1823
        while ($parent->isChild()) {
1824
            $parent = $parent->getParent();
1825
            ++$depth;
1826
        }
1827
1828
        return $depth;
1829
    }
1830
1831
    final public function getCurrentLeafChildAdmin()
1832
    {
1833
        $child = $this->getCurrentChildAdmin();
1834
1835
        if (null === $child) {
1836
            return;
1837
        }
1838
1839
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1840
            $child = $c;
1841
        }
1842
1843
        return $child;
1844
    }
1845
1846
    public function isChild()
1847
    {
1848
        return $this->parent instanceof AdminInterface;
1849
    }
1850
1851
    /**
1852
     * Returns true if the admin has children, false otherwise.
1853
     *
1854
     * @return bool if the admin has children
1855
     */
1856
    public function hasChildren()
1857
    {
1858
        return count($this->children) > 0;
1859
    }
1860
1861
    public function setUniqid($uniqid)
1862
    {
1863
        $this->uniqid = $uniqid;
1864
    }
1865
1866
    public function getUniqid()
1867
    {
1868
        if (!$this->uniqid) {
1869
            $this->uniqid = 's'.uniqid();
1870
        }
1871
1872
        return $this->uniqid;
1873
    }
1874
1875
    /**
1876
     * Returns the classname label.
1877
     *
1878
     * @return string the classname label
1879
     */
1880
    public function getClassnameLabel()
1881
    {
1882
        return $this->classnameLabel;
1883
    }
1884
1885
    public function getPersistentParameters()
1886
    {
1887
        $parameters = [];
1888
1889
        foreach ($this->getExtensions() as $extension) {
1890
            $params = $extension->getPersistentParameters($this);
1891
1892
            if (!is_array($params)) {
1893
                throw new \RuntimeException(sprintf('The %s::getPersistentParameters must return an array', get_class($extension)));
1894
            }
1895
1896
            $parameters = array_merge($parameters, $params);
1897
        }
1898
1899
        return $parameters;
1900
    }
1901
1902
    /**
1903
     * @param string $name
1904
     *
1905
     * @return null|mixed
1906
     */
1907
    public function getPersistentParameter($name)
1908
    {
1909
        $parameters = $this->getPersistentParameters();
1910
1911
        return isset($parameters[$name]) ? $parameters[$name] : null;
1912
    }
1913
1914
    public function getBreadcrumbs($action)
1915
    {
1916
        @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...
1917
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
1918
            ' Use Sonata\AdminBundle\Admin\BreadcrumbsBuilder::getBreadcrumbs instead.',
1919
            E_USER_DEPRECATED
1920
        );
1921
1922
        return $this->getBreadcrumbsBuilder()->getBreadcrumbs($this, $action);
1923
    }
1924
1925
    /**
1926
     * Generates the breadcrumbs array.
1927
     *
1928
     * Note: the method will be called by the top admin instance (parent => child)
1929
     *
1930
     * @param string $action
1931
     *
1932
     * @return array
1933
     */
1934
    public function buildBreadcrumbs($action, MenuItemInterface $menu = null)
1935
    {
1936
        @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...
1937
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.',
1938
            E_USER_DEPRECATED
1939
        );
1940
1941
        if (isset($this->breadcrumbs[$action])) {
1942
            return $this->breadcrumbs[$action];
1943
        }
1944
1945
        return $this->breadcrumbs[$action] = $this->getBreadcrumbsBuilder()
1946
            ->buildBreadcrumbs($this, $action, $menu);
1947
    }
1948
1949
    /**
1950
     * NEXT_MAJOR : remove this method.
1951
     *
1952
     * @return BreadcrumbsBuilderInterface
1953
     */
1954
    final public function getBreadcrumbsBuilder()
1955
    {
1956
        @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...
1957
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
1958
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
1959
            E_USER_DEPRECATED
1960
        );
1961
        if (null === $this->breadcrumbsBuilder) {
1962
            $this->breadcrumbsBuilder = new BreadcrumbsBuilder(
1963
                $this->getConfigurationPool()->getContainer()->getParameter('sonata.admin.configuration.breadcrumbs')
1964
            );
1965
        }
1966
1967
        return $this->breadcrumbsBuilder;
1968
    }
1969
1970
    /**
1971
     * NEXT_MAJOR : remove this method.
1972
     *
1973
     * @return AbstractAdmin
1974
     */
1975
    final public function setBreadcrumbsBuilder(BreadcrumbsBuilderInterface $value)
1976
    {
1977
        @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...
1978
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
1979
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
1980
            E_USER_DEPRECATED
1981
        );
1982
        $this->breadcrumbsBuilder = $value;
1983
1984
        return $this;
1985
    }
1986
1987
    public function setCurrentChild($currentChild)
1988
    {
1989
        $this->currentChild = $currentChild;
1990
    }
1991
1992
    public function getCurrentChild()
1993
    {
1994
        return $this->currentChild;
1995
    }
1996
1997
    /**
1998
     * Returns the current child admin instance.
1999
     *
2000
     * @return AdminInterface|null the current child admin instance
2001
     */
2002
    public function getCurrentChildAdmin()
2003
    {
2004
        foreach ($this->children as $children) {
2005
            if ($children->getCurrentChild()) {
2006
                return $children;
2007
            }
2008
        }
2009
    }
2010
2011
    public function trans($id, array $parameters = [], $domain = null, $locale = null)
2012
    {
2013
        @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...
2014
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2015
            E_USER_DEPRECATED
2016
        );
2017
2018
        $domain = $domain ?: $this->getTranslationDomain();
2019
2020
        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...
2021
    }
2022
2023
    /**
2024
     * Translate a message id.
2025
     *
2026
     * NEXT_MAJOR: remove this method
2027
     *
2028
     * @param string      $id
2029
     * @param int         $count
2030
     * @param string|null $domain
2031
     * @param string|null $locale
2032
     *
2033
     * @return string the translated string
2034
     *
2035
     * @deprecated since 3.9, to be removed with 4.0
2036
     */
2037
    public function transChoice($id, $count, array $parameters = [], $domain = null, $locale = null)
2038
    {
2039
        @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...
2040
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2041
            E_USER_DEPRECATED
2042
        );
2043
2044
        $domain = $domain ?: $this->getTranslationDomain();
2045
2046
        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...
2047
    }
2048
2049
    public function setTranslationDomain($translationDomain)
2050
    {
2051
        $this->translationDomain = $translationDomain;
2052
    }
2053
2054
    public function getTranslationDomain()
2055
    {
2056
        return $this->translationDomain;
2057
    }
2058
2059
    /**
2060
     * {@inheritdoc}
2061
     *
2062
     * NEXT_MAJOR: remove this method
2063
     *
2064
     * @deprecated since 3.9, to be removed with 4.0
2065
     */
2066
    public function setTranslator(TranslatorInterface $translator)
2067
    {
2068
        $args = func_get_args();
2069
        if (isset($args[1]) && $args[1]) {
2070
            @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...
2071
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2072
                E_USER_DEPRECATED
2073
            );
2074
        }
2075
2076
        $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...
2077
    }
2078
2079
    /**
2080
     * {@inheritdoc}
2081
     *
2082
     * NEXT_MAJOR: remove this method
2083
     *
2084
     * @deprecated since 3.9, to be removed with 4.0
2085
     */
2086
    public function getTranslator()
2087
    {
2088
        @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...
2089
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2090
            E_USER_DEPRECATED
2091
        );
2092
2093
        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...
2094
    }
2095
2096
    public function getTranslationLabel($label, $context = '', $type = '')
2097
    {
2098
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2099
    }
2100
2101
    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...
2102
    {
2103
        $this->request = $request;
2104
2105
        foreach ($this->getChildren() as $children) {
2106
            $children->setRequest($request);
2107
        }
2108
    }
2109
2110
    public function getRequest()
2111
    {
2112
        if (!$this->request) {
2113
            throw new \RuntimeException('The Request object has not been set');
2114
        }
2115
2116
        return $this->request;
2117
    }
2118
2119
    public function hasRequest()
2120
    {
2121
        return null !== $this->request;
2122
    }
2123
2124
    public function setFormContractor(FormContractorInterface $formBuilder)
2125
    {
2126
        $this->formContractor = $formBuilder;
2127
    }
2128
2129
    /**
2130
     * @return FormContractorInterface
2131
     */
2132
    public function getFormContractor()
2133
    {
2134
        return $this->formContractor;
2135
    }
2136
2137
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder)
2138
    {
2139
        $this->datagridBuilder = $datagridBuilder;
2140
    }
2141
2142
    public function getDatagridBuilder()
2143
    {
2144
        return $this->datagridBuilder;
2145
    }
2146
2147
    public function setListBuilder(ListBuilderInterface $listBuilder)
2148
    {
2149
        $this->listBuilder = $listBuilder;
2150
    }
2151
2152
    public function getListBuilder()
2153
    {
2154
        return $this->listBuilder;
2155
    }
2156
2157
    public function setShowBuilder(ShowBuilderInterface $showBuilder)
2158
    {
2159
        $this->showBuilder = $showBuilder;
2160
    }
2161
2162
    /**
2163
     * @return ShowBuilderInterface
2164
     */
2165
    public function getShowBuilder()
2166
    {
2167
        return $this->showBuilder;
2168
    }
2169
2170
    public function setConfigurationPool(Pool $configurationPool)
2171
    {
2172
        $this->configurationPool = $configurationPool;
2173
    }
2174
2175
    /**
2176
     * @return Pool
2177
     */
2178
    public function getConfigurationPool()
2179
    {
2180
        return $this->configurationPool;
2181
    }
2182
2183
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator)
2184
    {
2185
        $this->routeGenerator = $routeGenerator;
2186
    }
2187
2188
    /**
2189
     * @return RouteGeneratorInterface
2190
     */
2191
    public function getRouteGenerator()
2192
    {
2193
        return $this->routeGenerator;
2194
    }
2195
2196
    public function getCode()
2197
    {
2198
        return $this->code;
2199
    }
2200
2201
    /**
2202
     * NEXT_MAJOR: Remove this function.
2203
     *
2204
     * @deprecated This method is deprecated since 3.24 and will be removed in 4.0
2205
     *
2206
     * @param string $baseCodeRoute
2207
     */
2208
    public function setBaseCodeRoute($baseCodeRoute)
2209
    {
2210
        @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...
2211
            'The '.__METHOD__.' is deprecated since 3.24 and will be removed in 4.0.',
2212
            E_USER_DEPRECATED
2213
        );
2214
2215
        $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...
2216
    }
2217
2218
    public function getBaseCodeRoute()
2219
    {
2220
        // NEXT_MAJOR: Uncomment the following lines.
2221
        // if ($this->isChild()) {
2222
        //     return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2223
        // }
2224
        //
2225
        // return $this->getCode();
2226
2227
        // NEXT_MAJOR: Remove all the code below.
2228
        if ($this->isChild()) {
2229
            $parentCode = $this->getParent()->getCode();
2230
2231
            if ($this->getParent()->isChild()) {
2232
                $parentCode = $this->getParent()->getBaseCodeRoute();
2233
            }
2234
2235
            return $parentCode.'|'.$this->getCode();
2236
        }
2237
2238
        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...
2239
    }
2240
2241
    public function getModelManager()
2242
    {
2243
        return $this->modelManager;
2244
    }
2245
2246
    public function setModelManager(ModelManagerInterface $modelManager)
2247
    {
2248
        $this->modelManager = $modelManager;
2249
    }
2250
2251
    public function getManagerType()
2252
    {
2253
        return $this->managerType;
2254
    }
2255
2256
    /**
2257
     * @param string $type
2258
     */
2259
    public function setManagerType($type)
2260
    {
2261
        $this->managerType = $type;
2262
    }
2263
2264
    public function getObjectIdentifier()
2265
    {
2266
        return $this->getCode();
2267
    }
2268
2269
    /**
2270
     * Set the roles and permissions per role.
2271
     */
2272
    public function setSecurityInformation(array $information)
2273
    {
2274
        $this->securityInformation = $information;
2275
    }
2276
2277
    public function getSecurityInformation()
2278
    {
2279
        return $this->securityInformation;
2280
    }
2281
2282
    /**
2283
     * Return the list of permissions the user should have in order to display the admin.
2284
     *
2285
     * @param string $context
2286
     *
2287
     * @return array
2288
     */
2289
    public function getPermissionsShow($context)
2290
    {
2291
        switch ($context) {
2292
            case self::CONTEXT_DASHBOARD:
2293
            case self::CONTEXT_MENU:
2294
            default:
2295
                return ['LIST'];
2296
        }
2297
    }
2298
2299
    public function showIn($context)
2300
    {
2301
        switch ($context) {
2302
            case self::CONTEXT_DASHBOARD:
2303
            case self::CONTEXT_MENU:
2304
            default:
2305
                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...
2306
        }
2307
    }
2308
2309
    public function createObjectSecurity($object)
2310
    {
2311
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2312
    }
2313
2314
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
2315
    {
2316
        $this->securityHandler = $securityHandler;
2317
    }
2318
2319
    public function getSecurityHandler()
2320
    {
2321
        return $this->securityHandler;
2322
    }
2323
2324
    public function isGranted($name, $object = null)
2325
    {
2326
        $key = md5(json_encode($name).($object ? '/'.spl_object_hash($object) : ''));
2327
2328
        if (!array_key_exists($key, $this->cacheIsGranted)) {
2329
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2330
        }
2331
2332
        return $this->cacheIsGranted[$key];
2333
    }
2334
2335
    public function getUrlsafeIdentifier($entity)
2336
    {
2337
        return $this->getModelManager()->getUrlsafeIdentifier($entity);
2338
    }
2339
2340
    public function getNormalizedIdentifier($entity)
2341
    {
2342
        return $this->getModelManager()->getNormalizedIdentifier($entity);
2343
    }
2344
2345
    public function id($entity)
2346
    {
2347
        return $this->getNormalizedIdentifier($entity);
2348
    }
2349
2350
    public function setValidator($validator)
2351
    {
2352
        // NEXT_MAJOR: Move ValidatorInterface check to method signature
2353
        if (!$validator instanceof ValidatorInterface) {
2354
            throw new \InvalidArgumentException(
2355
                'Argument 1 must be an instance of Symfony\Component\Validator\Validator\ValidatorInterface'
2356
            );
2357
        }
2358
2359
        $this->validator = $validator;
2360
    }
2361
2362
    public function getValidator()
2363
    {
2364
        return $this->validator;
2365
    }
2366
2367
    public function getShow()
2368
    {
2369
        $this->buildShow();
2370
2371
        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...
2372
    }
2373
2374
    public function setFormTheme(array $formTheme)
2375
    {
2376
        $this->formTheme = $formTheme;
2377
    }
2378
2379
    public function getFormTheme()
2380
    {
2381
        return $this->formTheme;
2382
    }
2383
2384
    public function setFilterTheme(array $filterTheme)
2385
    {
2386
        $this->filterTheme = $filterTheme;
2387
    }
2388
2389
    public function getFilterTheme()
2390
    {
2391
        return $this->filterTheme;
2392
    }
2393
2394
    public function addExtension(AdminExtensionInterface $extension)
2395
    {
2396
        $this->extensions[] = $extension;
2397
    }
2398
2399
    public function getExtensions()
2400
    {
2401
        return $this->extensions;
2402
    }
2403
2404
    public function setMenuFactory(MenuFactoryInterface $menuFactory)
2405
    {
2406
        $this->menuFactory = $menuFactory;
2407
    }
2408
2409
    public function getMenuFactory()
2410
    {
2411
        return $this->menuFactory;
2412
    }
2413
2414
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder)
2415
    {
2416
        $this->routeBuilder = $routeBuilder;
2417
    }
2418
2419
    public function getRouteBuilder()
2420
    {
2421
        return $this->routeBuilder;
2422
    }
2423
2424
    public function toString($object)
2425
    {
2426
        if (!is_object($object)) {
2427
            return '';
2428
        }
2429
2430
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2431
            return (string) $object;
2432
        }
2433
2434
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2435
    }
2436
2437
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy)
2438
    {
2439
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2440
    }
2441
2442
    public function getLabelTranslatorStrategy()
2443
    {
2444
        return $this->labelTranslatorStrategy;
2445
    }
2446
2447
    public function supportsPreviewMode()
2448
    {
2449
        return $this->supportsPreviewMode;
2450
    }
2451
2452
    /**
2453
     * Set custom per page options.
2454
     */
2455
    public function setPerPageOptions(array $options)
2456
    {
2457
        $this->perPageOptions = $options;
2458
    }
2459
2460
    /**
2461
     * Returns predefined per page options.
2462
     *
2463
     * @return array
2464
     */
2465
    public function getPerPageOptions()
2466
    {
2467
        return $this->perPageOptions;
2468
    }
2469
2470
    /**
2471
     * Set pager type.
2472
     *
2473
     * @param string $pagerType
2474
     */
2475
    public function setPagerType($pagerType)
2476
    {
2477
        $this->pagerType = $pagerType;
2478
    }
2479
2480
    /**
2481
     * Get pager type.
2482
     *
2483
     * @return string
2484
     */
2485
    public function getPagerType()
2486
    {
2487
        return $this->pagerType;
2488
    }
2489
2490
    /**
2491
     * Returns true if the per page value is allowed, false otherwise.
2492
     *
2493
     * @param int $perPage
2494
     *
2495
     * @return bool
2496
     */
2497
    public function determinedPerPageValue($perPage)
2498
    {
2499
        return in_array($perPage, $this->perPageOptions);
2500
    }
2501
2502
    public function isAclEnabled()
2503
    {
2504
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2505
    }
2506
2507
    public function getObjectMetadata($object)
2508
    {
2509
        return new Metadata($this->toString($object));
2510
    }
2511
2512
    public function getListModes()
2513
    {
2514
        return $this->listModes;
2515
    }
2516
2517
    public function setListMode($mode)
2518
    {
2519
        if (!$this->hasRequest()) {
2520
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2521
        }
2522
2523
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2524
    }
2525
2526
    public function getListMode()
2527
    {
2528
        if (!$this->hasRequest()) {
2529
            return 'list';
2530
        }
2531
2532
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2533
    }
2534
2535
    public function getAccessMapping()
2536
    {
2537
        return $this->accessMapping;
2538
    }
2539
2540
    public function checkAccess($action, $object = null)
2541
    {
2542
        $access = $this->getAccess();
2543
2544
        if (!array_key_exists($action, $access)) {
2545
            throw new \InvalidArgumentException(sprintf(
2546
                'Action "%s" could not be found in access mapping.'
2547
                .' Please make sure your action is defined into your admin class accessMapping property.',
2548
                $action
2549
            ));
2550
        }
2551
2552
        if (!is_array($access[$action])) {
2553
            $access[$action] = [$access[$action]];
2554
        }
2555
2556
        foreach ($access[$action] as $role) {
2557
            if (false === $this->isGranted($role, $object)) {
2558
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2559
            }
2560
        }
2561
    }
2562
2563
    /**
2564
     * Hook to handle access authorization, without throw Exception.
2565
     *
2566
     * @param string $action
2567
     * @param object $object
2568
     *
2569
     * @return bool
2570
     */
2571
    public function hasAccess($action, $object = null)
2572
    {
2573
        $access = $this->getAccess();
2574
2575
        if (!array_key_exists($action, $access)) {
2576
            return false;
2577
        }
2578
2579
        if (!is_array($access[$action])) {
2580
            $access[$action] = [$access[$action]];
2581
        }
2582
2583
        foreach ($access[$action] as $role) {
2584
            if (false === $this->isGranted($role, $object)) {
2585
                return false;
2586
            }
2587
        }
2588
2589
        return true;
2590
    }
2591
2592
    public function configureActionButtons($action, $object = null)
2593
    {
2594
        $list = [];
2595
2596
        if (in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'])
2597
            && $this->hasAccess('create')
2598
            && $this->hasRoute('create')
2599
        ) {
2600
            $list['create'] = [
2601
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2602
                'template' => $this->getTemplate('button_create'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2603
//                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2604
            ];
2605
        }
2606
2607
        if (in_array($action, ['show', 'delete', 'acl', 'history'])
2608
            && $this->canAccessObject('edit', $object)
2609
            && $this->hasRoute('edit')
2610
        ) {
2611
            $list['edit'] = [
2612
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2613
                'template' => $this->getTemplate('button_edit'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2614
                //'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2615
            ];
2616
        }
2617
2618
        if (in_array($action, ['show', 'edit', 'acl'])
2619
            && $this->canAccessObject('history', $object)
2620
            && $this->hasRoute('history')
2621
        ) {
2622
            $list['history'] = [
2623
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2624
                'template' => $this->getTemplate('button_history'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2625
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2626
            ];
2627
        }
2628
2629
        if (in_array($action, ['edit', 'history'])
2630
            && $this->isAclEnabled()
2631
            && $this->canAccessObject('acl', $object)
2632
            && $this->hasRoute('acl')
2633
        ) {
2634
            $list['acl'] = [
2635
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2636
                'template' => $this->getTemplate('button_acl'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2637
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2638
            ];
2639
        }
2640
2641
        if (in_array($action, ['edit', 'history', 'acl'])
2642
            && $this->canAccessObject('show', $object)
2643
            && count($this->getShow()) > 0
2644
            && $this->hasRoute('show')
2645
        ) {
2646
            $list['show'] = [
2647
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2648
                'template' => $this->getTemplate('button_show'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2649
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2650
            ];
2651
        }
2652
2653
        if (in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'])
2654
            && $this->hasAccess('list')
2655
            && $this->hasRoute('list')
2656
        ) {
2657
            $list['list'] = [
2658
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2659
                'template' => $this->getTemplate('button_list'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2660
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2661
            ];
2662
        }
2663
2664
        return $list;
2665
    }
2666
2667
    /**
2668
     * @param string $action
2669
     * @param mixed  $object
2670
     *
2671
     * @return array
2672
     */
2673
    public function getActionButtons($action, $object = null)
2674
    {
2675
        $list = $this->configureActionButtons($action, $object);
2676
2677
        foreach ($this->getExtensions() as $extension) {
2678
            // TODO: remove method check in next major release
2679
            if (method_exists($extension, 'configureActionButtons')) {
2680
                $list = $extension->configureActionButtons($this, $list, $action, $object);
2681
            }
2682
        }
2683
2684
        return $list;
2685
    }
2686
2687
    /**
2688
     * Get the list of actions that can be accessed directly from the dashboard.
2689
     *
2690
     * @return array
2691
     */
2692
    public function getDashboardActions()
2693
    {
2694
        $actions = [];
2695
2696
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2697
            $actions['create'] = [
2698
                'label' => 'link_add',
2699
                'translation_domain' => 'SonataAdminBundle',
2700
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2701
                'template' => $this->getTemplate('action_create'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2702
                // 'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2703
                'url' => $this->generateUrl('create'),
2704
                'icon' => 'plus-circle',
2705
            ];
2706
        }
2707
2708
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2709
            $actions['list'] = [
2710
                'label' => 'link_list',
2711
                'translation_domain' => 'SonataAdminBundle',
2712
                'url' => $this->generateUrl('list'),
2713
                'icon' => 'list',
2714
            ];
2715
        }
2716
2717
        return $actions;
2718
    }
2719
2720
    /**
2721
     * Setting to true will enable mosaic button for the admin screen.
2722
     * Setting to false will hide mosaic button for the admin screen.
2723
     *
2724
     * @param bool $isShown
2725
     */
2726
    final public function showMosaicButton($isShown)
2727
    {
2728
        if ($isShown) {
2729
            $this->listModes['mosaic'] = ['class' => self::MOSAIC_ICON_CLASS];
2730
        } else {
2731
            unset($this->listModes['mosaic']);
2732
        }
2733
    }
2734
2735
    /**
2736
     * @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...
2737
     */
2738
    final public function getSearchResultLink($object)
2739
    {
2740
        foreach ($this->searchResultActions as $action) {
2741
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2742
                return $this->generateObjectUrl($action, $object);
2743
            }
2744
        }
2745
    }
2746
2747
    /**
2748
     * Checks if a filter type is set to a default value.
2749
     *
2750
     * @param string $name
2751
     *
2752
     * @return bool
2753
     */
2754
    final public function isDefaultFilter($name)
2755
    {
2756
        $filter = $this->getFilterParameters();
2757
        $default = $this->getDefaultFilterValues();
2758
2759
        if (!array_key_exists($name, $filter) || !array_key_exists($name, $default)) {
2760
            return false;
2761
        }
2762
2763
        return $filter[$name] == $default[$name];
2764
    }
2765
2766
    /**
2767
     * Check object existence and access, without throw Exception.
2768
     *
2769
     * @param string $action
2770
     * @param object $object
2771
     *
2772
     * @return bool
2773
     */
2774
    public function canAccessObject($action, $object)
2775
    {
2776
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2777
    }
2778
2779
    /**
2780
     * @return MutableTemplateRegistryInterface
2781
     */
2782
    final protected function getTemplateRegistry()
2783
    {
2784
        return $this->templateRegistry;
2785
    }
2786
2787
    /**
2788
     * Returns a list of default filters.
2789
     *
2790
     * @return array
2791
     */
2792
    final protected function getDefaultFilterValues()
2793
    {
2794
        $defaultFilterValues = [];
2795
2796
        $this->configureDefaultFilterValues($defaultFilterValues);
2797
2798
        foreach ($this->getExtensions() as $extension) {
2799
            // NEXT_MAJOR: remove method check in next major release
2800
            if (method_exists($extension, 'configureDefaultFilterValues')) {
2801
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2802
            }
2803
        }
2804
2805
        return $defaultFilterValues;
2806
    }
2807
2808
    protected function configureFormFields(FormMapper $form)
2809
    {
2810
    }
2811
2812
    protected function configureListFields(ListMapper $list)
2813
    {
2814
    }
2815
2816
    protected function configureDatagridFilters(DatagridMapper $filter)
2817
    {
2818
    }
2819
2820
    protected function configureShowFields(ShowMapper $show)
2821
    {
2822
    }
2823
2824
    protected function configureRoutes(RouteCollection $collection)
2825
    {
2826
    }
2827
2828
    /**
2829
     * Allows you to customize batch actions.
2830
     *
2831
     * @param array $actions List of actions
2832
     *
2833
     * @return array
2834
     */
2835
    protected function configureBatchActions($actions)
2836
    {
2837
        return $actions;
2838
    }
2839
2840
    /**
2841
     * NEXT_MAJOR: remove this method.
2842
     *
2843
     * @return mixed
2844
     *
2845
     * @deprecated Use configureTabMenu instead
2846
     */
2847
    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...
2848
    {
2849
    }
2850
2851
    /**
2852
     * Configures the tab menu in your admin.
2853
     *
2854
     * @param string $action
2855
     *
2856
     * @return mixed
2857
     */
2858
    protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
2859
    {
2860
        // Use configureSideMenu not to mess with previous overrides
2861
        // TODO remove once deprecation period is over
2862
        $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...
2863
    }
2864
2865
    /**
2866
     * build the view FieldDescription array.
2867
     */
2868
    protected function buildShow()
2869
    {
2870
        if ($this->show) {
2871
            return;
2872
        }
2873
2874
        $this->show = new FieldDescriptionCollection();
2875
        $mapper = new ShowMapper($this->showBuilder, $this->show, $this);
2876
2877
        $this->configureShowFields($mapper);
2878
2879
        foreach ($this->getExtensions() as $extension) {
2880
            $extension->configureShowFields($mapper);
2881
        }
2882
    }
2883
2884
    /**
2885
     * build the list FieldDescription array.
2886
     */
2887
    protected function buildList()
2888
    {
2889
        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...
2890
            return;
2891
        }
2892
2893
        $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...
2894
2895
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
2896
2897
        if (count($this->getBatchActions()) > 0) {
2898
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2899
                $this->getClass(),
2900
                'batch',
2901
                [
2902
                    'label' => 'batch',
2903
                    'code' => '_batch',
2904
                    'sortable' => false,
2905
                    'virtual_field' => true,
2906
                ]
2907
            );
2908
2909
            $fieldDescription->setAdmin($this);
2910
            // NEXT_MAJOR: Remove this line and use commented line below it instead
2911
            $fieldDescription->setTemplate($this->getTemplate('batch'));
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2912
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
2913
2914
            $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...
2915
        }
2916
2917
        $this->configureListFields($mapper);
2918
2919
        foreach ($this->getExtensions() as $extension) {
2920
            $extension->configureListFields($mapper);
2921
        }
2922
2923
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
2924
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2925
                $this->getClass(),
2926
                'select',
2927
                [
2928
                    'label' => false,
2929
                    'code' => '_select',
2930
                    'sortable' => false,
2931
                    'virtual_field' => false,
2932
                ]
2933
            );
2934
2935
            $fieldDescription->setAdmin($this);
2936
            // NEXT_MAJOR: Remove this line and use commented line below it instead
2937
            $fieldDescription->setTemplate($this->getTemplate('select'));
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2938
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
2939
2940
            $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...
2941
        }
2942
    }
2943
2944
    /**
2945
     * Build the form FieldDescription collection.
2946
     */
2947
    protected function buildForm()
2948
    {
2949
        if ($this->form) {
2950
            return;
2951
        }
2952
2953
        // append parent object if any
2954
        // todo : clean the way the Admin class can retrieve set the object
2955
        if ($this->isChild() && $this->getParentAssociationMapping()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getParentAssociationMapping() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2956
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
2957
2958
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
2959
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
2960
2961
            $object = $this->getSubject();
2962
2963
            $value = $propertyAccessor->getValue($object, $propertyPath);
2964
2965
            if (is_array($value) || ($value instanceof \Traversable && $value instanceof \ArrayAccess)) {
2966
                $value[] = $parent;
2967
                $propertyAccessor->setValue($object, $propertyPath, $value);
2968
            } else {
2969
                $propertyAccessor->setValue($object, $propertyPath, $parent);
2970
            }
2971
        }
2972
2973
        $formBuilder = $this->getFormBuilder();
2974
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
2975
            $this->preValidate($event->getData());
2976
        }, 100);
2977
2978
        $this->form = $formBuilder->getForm();
2979
    }
2980
2981
    /**
2982
     * Gets the subclass corresponding to the given name.
2983
     *
2984
     * @param string $name The name of the sub class
2985
     *
2986
     * @return string the subclass
2987
     */
2988
    protected function getSubClass($name)
2989
    {
2990
        if ($this->hasSubClass($name)) {
2991
            return $this->subClasses[$name];
2992
        }
2993
2994
        throw new \RuntimeException(sprintf(
2995
            'Unable to find the subclass `%s` for admin `%s`',
2996
            $name,
2997
            get_class($this)
2998
        ));
2999
    }
3000
3001
    /**
3002
     * Attach the inline validator to the model metadata, this must be done once per admin.
3003
     */
3004
    protected function attachInlineValidator()
3005
    {
3006
        $admin = $this;
3007
3008
        // add the custom inline validation option
3009
        $metadata = $this->validator->getMetadataFor($this->getClass());
3010
3011
        $metadata->addConstraint(new InlineConstraint([
3012
            'service' => $this,
3013
            'method' => function (ErrorElement $errorElement, $object) use ($admin) {
3014
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
3015
3016
                // This avoid the main validation to be cascaded to children
3017
                // The problem occurs when a model Page has a collection of Page as property
3018
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
3019
                    return;
3020
                }
3021
3022
                $admin->validate($errorElement, $object);
3023
3024
                foreach ($admin->getExtensions() as $extension) {
3025
                    $extension->validate($admin, $errorElement, $object);
3026
                }
3027
            },
3028
            'serializingWarning' => true,
3029
        ]));
3030
    }
3031
3032
    /**
3033
     * Predefine per page options.
3034
     */
3035
    protected function predefinePerPageOptions()
3036
    {
3037
        array_unshift($this->perPageOptions, $this->maxPerPage);
3038
        $this->perPageOptions = array_unique($this->perPageOptions);
3039
        sort($this->perPageOptions);
3040
    }
3041
3042
    /**
3043
     * Return list routes with permissions name.
3044
     *
3045
     * @return array
3046
     */
3047
    protected function getAccess()
3048
    {
3049
        $access = array_merge([
3050
            'acl' => 'MASTER',
3051
            'export' => 'EXPORT',
3052
            'historyCompareRevisions' => 'EDIT',
3053
            'historyViewRevision' => 'EDIT',
3054
            'history' => 'EDIT',
3055
            'edit' => 'EDIT',
3056
            'show' => 'VIEW',
3057
            'create' => 'CREATE',
3058
            'delete' => 'DELETE',
3059
            'batchDelete' => 'DELETE',
3060
            'list' => 'LIST',
3061
        ], $this->getAccessMapping());
3062
3063
        foreach ($this->extensions as $extension) {
3064
            // TODO: remove method check in next major release
3065
            if (method_exists($extension, 'getAccessMapping')) {
3066
                $access = array_merge($access, $extension->getAccessMapping($this));
3067
            }
3068
        }
3069
3070
        return $access;
3071
    }
3072
3073
    /**
3074
     * Returns a list of default filters.
3075
     */
3076
    protected function configureDefaultFilterValues(array &$filterValues)
3077
    {
3078
    }
3079
3080
    /**
3081
     * Build all the related urls to the current admin.
3082
     */
3083
    private function buildRoutes()
3084
    {
3085
        if ($this->loaded['routes']) {
3086
            return;
3087
        }
3088
3089
        $this->loaded['routes'] = true;
3090
3091
        $this->routes = new RouteCollection(
3092
            $this->getBaseCodeRoute(),
3093
            $this->getBaseRouteName(),
3094
            $this->getBaseRoutePattern(),
3095
            $this->getBaseControllerName()
3096
        );
3097
3098
        $this->routeBuilder->build($this, $this->routes);
3099
3100
        $this->configureRoutes($this->routes);
3101
3102
        foreach ($this->getExtensions() as $extension) {
3103
            $extension->configureRoutes($this, $this->routes);
3104
        }
3105
    }
3106
}
3107