Completed
Pull Request — master (#6204)
by
unknown
20:19 queued 06:45
created

AbstractAdmin::transChoice()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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

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...
570
571
        // NEXT_MAJOR: Remove this line.
572
        $this->datagridValues['_per_page'] = $this->maxPerPage;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$datagridValues has been deprecated with message: since sonata-project/admin-bundle 3.67, use configureDefaultSortValues() 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...
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$maxPerPage has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
573
    }
574
575
    /**
576
     * {@inheritdoc}
577
     */
578
    public function getExportFormats(): array
579
    {
580
        return [
581
            'json', 'xml', 'csv', 'xls',
582
        ];
583
    }
584
585
    /**
586
     * {@inheritdoc}
587
     */
588
    public function getExportFields(): array
589
    {
590
        $fields = $this->getModelManager()->getExportFields($this->getClass());
591
592
        foreach ($this->getExtensions() as $extension) {
593
            if (method_exists($extension, 'configureExportFields')) {
594
                $fields = $extension->configureExportFields($this, $fields);
595
            }
596
        }
597
598
        return $fields;
599
    }
600
601
    public function getDataSourceIterator(): SourceIteratorInterface
602
    {
603
        $datagrid = $this->getDatagrid();
604
        $datagrid->buildPager();
605
606
        $fields = [];
607
608
        foreach ($this->getExportFields() as $key => $field) {
609
            $label = $this->getTranslationLabel($field, 'export', 'label');
610
            $transLabel = $this->trans($label);
611
612
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
613
            // No translation key exists
614
            if ($transLabel === $label) {
615
                $fields[$key] = $field;
616
            } else {
617
                $fields[$transLabel] = $field;
618
            }
619
        }
620
621
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
622
    }
623
624
    public function validate(ErrorElement $errorElement, $object): void
625
    {
626
    }
627
628
    /**
629
     * define custom variable.
630
     */
631
    public function initialize(): void
632
    {
633
        if (!$this->classnameLabel) {
634
            /* NEXT_MAJOR: remove cast to string, null is not supposed to be
635
            supported but was documented as such */
636
            $this->classnameLabel = substr(
637
                (string) $this->getClass(),
638
                strrpos((string) $this->getClass(), '\\') + 1
639
            );
640
        }
641
642
        $this->configure();
643
    }
644
645
    public function update(object $object): object
646
    {
647
        $this->preUpdate($object);
648
        foreach ($this->extensions as $extension) {
649
            $extension->preUpdate($this, $object);
650
        }
651
652
        $result = $this->getModelManager()->update($object);
653
        // BC compatibility
654
        if (null !== $result) {
655
            $object = $result;
656
        }
657
658
        $this->postUpdate($object);
659
        foreach ($this->extensions as $extension) {
660
            $extension->postUpdate($this, $object);
661
        }
662
663
        return $object;
664
    }
665
666
    public function create(object $object): object
667
    {
668
        $this->prePersist($object);
669
        foreach ($this->extensions as $extension) {
670
            $extension->prePersist($this, $object);
671
        }
672
673
        $result = $this->getModelManager()->create($object);
674
        // BC compatibility
675
        if (null !== $result) {
676
            $object = $result;
677
        }
678
679
        $this->postPersist($object);
680
        foreach ($this->extensions as $extension) {
681
            $extension->postPersist($this, $object);
682
        }
683
684
        $this->createObjectSecurity($object);
685
686
        return $object;
687
    }
688
689
    public function delete(object $object): void
690
    {
691
        $this->preRemove($object);
692
        foreach ($this->extensions as $extension) {
693
            $extension->preRemove($this, $object);
694
        }
695
696
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
697
        $this->getModelManager()->delete($object);
698
699
        $this->postRemove($object);
700
        foreach ($this->extensions as $extension) {
701
            $extension->postRemove($this, $object);
702
        }
703
    }
704
705
    public function preValidate(object $object): void
706
    {
707
    }
708
709
    public function preUpdate(object $object): void
710
    {
711
    }
712
713
    public function postUpdate(object $object): void
714
    {
715
    }
716
717
    public function prePersist(object $object): void
718
    {
719
    }
720
721
    public function postPersist(object $object): void
722
    {
723
    }
724
725
    public function preRemove(object $object): void
726
    {
727
    }
728
729
    public function postRemove(object $object): void
730
    {
731
    }
732
733
    public function preBatchAction(string $actionName, ProxyQueryInterface $query, array &$idx, bool $allElements = false): void
734
    {
735
    }
736
737
    public function getFilterParameters(): array
738
    {
739
        $parameters = [];
740
741
        // build the values array
742
        if ($this->hasRequest()) {
743
            $filters = $this->request->query->get('filter', []);
744
            if (isset($filters['_page'])) {
745
                $filters['_page'] = (int) $filters['_page'];
746
            }
747
            if (isset($filters['_per_page'])) {
748
                $filters['_per_page'] = (int) $filters['_per_page'];
749
            }
750
751
            // if filter persistence is configured
752
            // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
753
            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 sonata-project/admin-bundle 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...
754
                // if reset filters is asked, remove from storage
755
                if ('reset' === $this->request->query->get('filters')) {
756
                    $this->filterPersister->reset($this->getCode());
757
                }
758
759
                // if no filters, fetch from storage
760
                // otherwise save to storage
761
                if (empty($filters)) {
762
                    $filters = $this->filterPersister->get($this->getCode());
763
                } else {
764
                    $this->filterPersister->set($this->getCode(), $filters);
765
                }
766
            }
767
768
            $parameters = array_merge(
769
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
770
                $this->datagridValues, // NEXT_MAJOR: Remove this line.
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$datagridValues has been deprecated with message: since sonata-project/admin-bundle 3.67, use configureDefaultSortValues() 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...
771
                $this->getDefaultSortValues(),
772
                $this->getDefaultFilterValues(),
773
                $filters
774
            );
775
776
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
777
                $parameters['_per_page'] = $this->getMaxPerPage();
778
            }
779
780
            // always force the parent value
781
            if ($this->isChild() && $this->getParentAssociationMapping()) {
782
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
783
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
784
            }
785
        }
786
787
        return $parameters;
788
    }
789
790
    /**
791
     * Returns the name of the parent related field, so the field can be use to set the default
792
     * value (ie the parent object) or to filter the object.
793
     *
794
     * @throws \InvalidArgumentException
795
     */
796
    public function getParentAssociationMapping(): ?string
797
    {
798
        // NEXT_MAJOR: remove array check
799
        if (\is_array($this->parentAssociationMapping) && $this->isChild()) {
800
            $parent = $this->getParent()->getCode();
801
802
            if (\array_key_exists($parent, $this->parentAssociationMapping)) {
803
                return $this->parentAssociationMapping[$parent];
804
            }
805
806
            throw new \InvalidArgumentException(sprintf(
807
                'There\'s no association between %s and %s.',
808
                $this->getCode(),
809
                $this->getParent()->getCode()
810
            ));
811
        }
812
813
        // NEXT_MAJOR: remove this line
814
        return $this->parentAssociationMapping;
815
    }
816
817
    final public function addParentAssociationMapping(string $code, string $value): void
818
    {
819
        $this->parentAssociationMapping[$code] = $value;
820
    }
821
822
    /**
823
     * Returns the baseRoutePattern used to generate the routing information.
824
     *
825
     * @throws \RuntimeException
826
     *
827
     * @return string the baseRoutePattern used to generate the routing information
828
     */
829
    public function getBaseRoutePattern(): string
830
    {
831
        if (null !== $this->cachedBaseRoutePattern) {
832
            return $this->cachedBaseRoutePattern;
833
        }
834
835
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
836
            $baseRoutePattern = $this->baseRoutePattern;
837
            if (!$this->baseRoutePattern) {
838
                preg_match(self::CLASS_REGEX, $this->class, $matches);
839
840
                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...
841
                    throw new \RuntimeException(sprintf(
842
                        'Please define a default `baseRoutePattern` value for the admin class `%s`',
843
                        static::class
844
                    ));
845
                }
846
                $baseRoutePattern = $this->urlize($matches[5], '-');
847
            }
848
849
            $this->cachedBaseRoutePattern = sprintf(
850
                '%s/%s/%s',
851
                $this->getParent()->getBaseRoutePattern(),
852
                $this->getParent()->getRouterIdParameter(),
853
                $baseRoutePattern
854
            );
855
        } elseif ($this->baseRoutePattern) {
856
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
857
        } else {
858
            preg_match(self::CLASS_REGEX, $this->class, $matches);
859
860
            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...
861
                throw new \RuntimeException(sprintf(
862
                    'Please define a default `baseRoutePattern` value for the admin class `%s`',
863
                    static::class
864
                ));
865
            }
866
867
            $this->cachedBaseRoutePattern = sprintf(
868
                '/%s%s/%s',
869
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
870
                $this->urlize($matches[3], '-'),
871
                $this->urlize($matches[5], '-')
872
            );
873
        }
874
875
        return $this->cachedBaseRoutePattern;
876
    }
877
878
    /**
879
     * Returns the baseRouteName used to generate the routing information.
880
     *
881
     * @throws \RuntimeException
882
     *
883
     * @return string the baseRouteName used to generate the routing information
884
     */
885
    public function getBaseRouteName(): string
886
    {
887
        if (null !== $this->cachedBaseRouteName) {
888
            return $this->cachedBaseRouteName;
889
        }
890
891
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
892
            $baseRouteName = $this->baseRouteName;
893
            if (!$this->baseRouteName) {
894
                preg_match(self::CLASS_REGEX, $this->class, $matches);
895
896
                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...
897
                    throw new \RuntimeException(sprintf(
898
                        'Cannot automatically determine base route name,'
899
                        .' please define a default `baseRouteName` value for the admin class `%s`',
900
                        static::class
901
                    ));
902
                }
903
                $baseRouteName = $this->urlize($matches[5]);
904
            }
905
906
            $this->cachedBaseRouteName = sprintf(
907
                '%s_%s',
908
                $this->getParent()->getBaseRouteName(),
909
                $baseRouteName
910
            );
911
        } elseif ($this->baseRouteName) {
912
            $this->cachedBaseRouteName = $this->baseRouteName;
913
        } else {
914
            preg_match(self::CLASS_REGEX, $this->class, $matches);
915
916
            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...
917
                throw new \RuntimeException(sprintf(
918
                    'Cannot automatically determine base route name,'
919
                    .' please define a default `baseRouteName` value for the admin class `%s`',
920
                    static::class
921
                ));
922
            }
923
924
            $this->cachedBaseRouteName = sprintf(
925
                'admin_%s%s_%s',
926
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
927
                $this->urlize($matches[3]),
928
                $this->urlize($matches[5])
929
            );
930
        }
931
932
        return $this->cachedBaseRouteName;
933
    }
934
935
    public function getClass(): string
936
    {
937
        if ($this->hasActiveSubClass()) {
938
            if ($this->hasParentFieldDescription()) {
939
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
940
            }
941
942
            $subClass = $this->getRequest()->query->get('subclass');
943
944
            if (!$this->hasSubClass($subClass)) {
945
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
946
            }
947
948
            return $this->getSubClass($subClass);
949
        }
950
951
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
952
        if ($this->subject && \is_object($this->subject)) {
953
            return ClassUtils::getClass($this->subject);
954
        }
955
956
        return $this->class;
957
    }
958
959
    public function getSubClasses(): array
960
    {
961
        return $this->subClasses;
962
    }
963
964
    /**
965
     * NEXT_MAJOR: remove this method.
966
     */
967
    public function addSubClass($subClass): void
968
    {
969
        @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...
970
            'Method "%s" is deprecated since sonata-project/admin-bundle 3.30 and will be removed in 4.0.',
971
            __METHOD__
972
        ), E_USER_DEPRECATED);
973
974
        if (!\in_array($subClass, $this->subClasses, true)) {
975
            $this->subClasses[] = $subClass;
976
        }
977
    }
978
979
    public function setSubClasses(array $subClasses): void
980
    {
981
        $this->subClasses = $subClasses;
982
    }
983
984
    public function hasSubClass(string $name): bool
985
    {
986
        return isset($this->subClasses[$name]);
987
    }
988
989
    public function hasActiveSubClass(): bool
990
    {
991
        if (\count($this->subClasses) > 0 && $this->request) {
992
            return null !== $this->getRequest()->query->get('subclass');
993
        }
994
995
        return false;
996
    }
997
998
    public function getActiveSubClass(): string
999
    {
1000
        if (!$this->hasActiveSubClass()) {
1001
            throw new \LogicException(sprintf(
1002
                'Admin "%s" has no active subclass.',
1003
                static::class
1004
            ));
1005
        }
1006
1007
        return $this->getSubClass($this->getActiveSubclassCode());
1008
    }
1009
1010
    public function getActiveSubclassCode(): string
1011
    {
1012
        if (!$this->hasActiveSubClass()) {
1013
            throw new \LogicException(sprintf(
1014
                'Admin "%s" has no active subclass.',
1015
                static::class
1016
            ));
1017
        }
1018
1019
        $subClass = $this->getRequest()->query->get('subclass');
1020
1021
        if (!$this->hasSubClass($subClass)) {
1022
            throw new \LogicException(sprintf(
1023
                'Admin "%s" has no active subclass.',
1024
                static::class
1025
            ));
1026
        }
1027
1028
        return $subClass;
1029
    }
1030
1031
    public function getBatchActions(): array
1032
    {
1033
        $actions = [];
1034
1035
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1036
            $actions['delete'] = [
1037
                'label' => 'action_delete',
1038
                'translation_domain' => 'SonataAdminBundle',
1039
                'ask_confirmation' => true, // by default always true
1040
            ];
1041
        }
1042
1043
        $actions = $this->configureBatchActions($actions);
1044
1045
        foreach ($this->getExtensions() as $extension) {
1046
            $actions = $extension->configureBatchActions($this, $actions);
1047
        }
1048
1049
        foreach ($actions  as $name => &$action) {
1050
            if (!\array_key_exists('label', $action)) {
1051
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1052
            }
1053
1054
            if (!\array_key_exists('translation_domain', $action)) {
1055
                $action['translation_domain'] = $this->getTranslationDomain();
1056
            }
1057
        }
1058
1059
        return $actions;
1060
    }
1061
1062
    /**
1063
     * NEXT_MAJOR: Create a `RouteCollectionInterface` and use as return type.
1064
     */
1065
    public function getRoutes(): RouteCollection
1066
    {
1067
        $this->buildRoutes();
1068
1069
        return $this->routes;
1070
    }
1071
1072
    public function getRouterIdParameter(): string
1073
    {
1074
        return sprintf('{%s}', $this->getIdParameter());
1075
    }
1076
1077
    public function getIdParameter(): string
1078
    {
1079
        $parameter = 'id';
1080
1081
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1082
            $parameter = sprintf('child%s', ucfirst($parameter));
1083
        }
1084
1085
        return $parameter;
1086
    }
1087
1088
    public function hasRoute(string $name): bool
1089
    {
1090
        if (!$this->routeGenerator) {
1091
            throw new \RuntimeException('RouteGenerator cannot be null');
1092
        }
1093
1094
        return $this->routeGenerator->hasAdminRoute($this, $name);
1095
    }
1096
1097
    public function isCurrentRoute(string $name, ?string $adminCode = null): bool
1098
    {
1099
        if (!$this->hasRequest()) {
1100
            return false;
1101
        }
1102
1103
        $request = $this->getRequest();
1104
        $route = $request->get('_route');
1105
1106
        if ($adminCode) {
1107
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1108
        } else {
1109
            $admin = $this;
1110
        }
1111
1112
        if (!$admin) {
1113
            return false;
1114
        }
1115
1116
        return sprintf('%s_%s', $admin->getBaseRouteName(), $name) === $route;
1117
    }
1118
1119
    public function generateObjectUrl(string $name, object $object, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1120
    {
1121
        $parameters['id'] = $this->getUrlSafeIdentifier($object);
1122
1123
        return $this->generateUrl($name, $parameters, $referenceType);
1124
    }
1125
1126
    public function generateUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1127
    {
1128
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $referenceType);
1129
    }
1130
1131
    public function generateMenuUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): array
1132
    {
1133
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $referenceType);
1134
    }
1135
1136
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry): void
1137
    {
1138
        $this->templateRegistry = $templateRegistry;
1139
    }
1140
1141
    /**
1142
     * @param array<string, string> $templates
1143
     */
1144
    public function setTemplates(array $templates): void
1145
    {
1146
        $this->getTemplateRegistry()->setTemplates($templates);
1147
    }
1148
1149
    /**
1150
     * {@inheritdoc}
1151
     */
1152
    public function setTemplate(string $name, string $template): void
1153
    {
1154
        $this->getTemplateRegistry()->setTemplate($name, $template);
1155
    }
1156
1157
    public function getNewInstance(): object
1158
    {
1159
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1160
1161
        $this->appendParentObject($object);
1162
1163
        foreach ($this->getExtensions() as $extension) {
1164
            $extension->alterNewInstance($this, $object);
1165
        }
1166
1167
        return $object;
1168
    }
1169
1170
    public function getFormBuilder(): FormBuilderInterface
1171
    {
1172
        $this->formOptions['data_class'] = $this->getClass();
1173
1174
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1175
            $this->getUniqid(),
1176
            $this->formOptions
1177
        );
1178
1179
        $this->defineFormBuilder($formBuilder);
1180
1181
        return $formBuilder;
1182
    }
1183
1184
    /**
1185
     * This method is being called by the main admin class and the child class,
1186
     * the getFormBuilder is only call by the main admin class.
1187
     */
1188
    public function defineFormBuilder(FormBuilderInterface $formBuilder): void
1189
    {
1190
        if (!$this->hasSubject()) {
1191
            @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...
1192
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.65'
1193
                .' and will throw an exception in 4.0. Use %s::setSubject() to set the subject.',
1194
                __METHOD__,
1195
                __CLASS__
1196
            ), E_USER_DEPRECATED);
1197
            // NEXT_MAJOR : remove the previous `trigger_error()` call and uncomment the following exception
1198
            // throw new \LogicException(sprintf(
1199
            //    'Admin "%s" has no subject.',
1200
            //    static::class
1201
            // ));
1202
        }
1203
1204
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1205
1206
        $this->configureFormFields($mapper);
1207
1208
        foreach ($this->getExtensions() as $extension) {
1209
            $extension->configureFormFields($mapper);
1210
        }
1211
1212
        $this->attachInlineValidator();
1213
    }
1214
1215
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription): void
1216
    {
1217
        $pool = $this->getConfigurationPool();
1218
1219
        $adminCode = $fieldDescription->getOption('admin_code');
1220
1221
        if (null !== $adminCode) {
1222
            if (!$pool->hasAdminByAdminCode($adminCode)) {
1223
                return;
1224
            }
1225
1226
            $admin = $pool->getAdminByAdminCode($adminCode);
1227
        } else {
1228
            // NEXT_MAJOR: Remove the check and use `getTargetModel`.
1229
            if (method_exists($fieldDescription, 'getTargetModel')) {
1230
                $targetModel = $fieldDescription->getTargetModel();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sonata\AdminBundle\Admin\FieldDescriptionInterface as the method getTargetModel() does only exist in the following implementations of said interface: Sonata\AdminBundle\Tests...\Admin\FieldDescription, Sonata\AdminBundle\Tests...\Admin\FieldDescription.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1231
            } else {
1232
                $targetModel = $fieldDescription->getTargetEntity();
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...face::getTargetEntity() has been deprecated with message: since sonata-project/admin-bundle 3.69. Use `getTargetModel()` 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...
1233
            }
1234
1235
            if (!$pool->hasAdminByClass($targetModel)) {
1236
                return;
1237
            }
1238
1239
            $admin = $pool->getAdminByClass($targetModel);
1240
        }
1241
1242
        if ($this->hasRequest()) {
1243
            $admin->setRequest($this->getRequest());
1244
        }
1245
1246
        $fieldDescription->setAssociationAdmin($admin);
0 ignored issues
show
Bug introduced by
It seems like $admin defined by $pool->getAdminByClass($targetModel) on line 1239 can be null; however, Sonata\AdminBundle\Admin...::setAssociationAdmin() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1247
    }
1248
1249
    public function getObject($id): ?object
1250
    {
1251
        $object = $this->getModelManager()->find($this->getClass(), $id);
1252
        foreach ($this->getExtensions() as $extension) {
1253
            $extension->alterObject($this, $object);
0 ignored issues
show
Documentation introduced by
$object is of type object|null, but the function expects a object<Sonata\AdminBundle\Admin\object>.

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...
1254
        }
1255
1256
        return $object;
1257
    }
1258
1259
    public function getForm(): ?FormInterface
1260
    {
1261
        $this->buildForm();
1262
1263
        return $this->form;
1264
    }
1265
1266
    public function getList(): ?FieldDescriptionCollection
1267
    {
1268
        $this->buildList();
1269
1270
        return $this->list;
1271
    }
1272
1273
    /**
1274
     * @final since sonata-project/admin-bundle 3.63.0
1275
     */
1276
    public function createQuery($context = 'list'): ProxyQueryInterface
1277
    {
1278
        if (\func_num_args() > 0) {
1279
            @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...
1280
                'The $context argument of %s is deprecated since 3.3, to be removed in 4.0.',
1281
                __METHOD__
1282
            ), E_USER_DEPRECATED);
1283
        }
1284
1285
        $query = $this->getModelManager()->createQuery($this->getClass());
1286
1287
        $query = $this->configureQuery($query);
1288
        foreach ($this->extensions as $extension) {
1289
            $extension->configureQuery($this, $query, $context);
1290
        }
1291
1292
        return $query;
1293
    }
1294
1295
    public function getDatagrid(): DatagridInterface
1296
    {
1297
        $this->buildDatagrid();
1298
1299
        return $this->datagrid;
1300
    }
1301
1302
    public function buildTabMenu(string $action, ?AdminInterface $childAdmin = null): ItemInterface
1303
    {
1304
        if ($this->loaded['tab_menu']) {
1305
            return $this->menu;
1306
        }
1307
1308
        $this->loaded['tab_menu'] = true;
1309
1310
        $menu = $this->menuFactory->createItem('root');
1311
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1312
        $menu->setExtra('translation_domain', $this->translationDomain);
1313
1314
        // Prevents BC break with KnpMenuBundle v1.x
1315
        if (method_exists($menu, 'setCurrentUri')) {
1316
            $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...
1317
        }
1318
1319
        $this->configureTabMenu($menu, $action, $childAdmin);
1320
1321
        foreach ($this->getExtensions() as $extension) {
1322
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1323
        }
1324
1325
        $this->menu = $menu;
1326
1327
        return $this->menu;
1328
    }
1329
1330
    public function getSideMenu(string $action, ?AdminInterface $childAdmin = null): ItemInterface
1331
    {
1332
        if ($this->isChild()) {
1333
            return $this->getParent()->getSideMenu($action, $this);
1334
        }
1335
1336
        $this->buildTabMenu($action, $childAdmin);
1337
1338
        return $this->menu;
1339
    }
1340
1341
    public function getRootCode(): string
1342
    {
1343
        return $this->getRoot()->getCode();
1344
    }
1345
1346
    public function getRoot(): AdminInterface
1347
    {
1348
        if (!$this->hasParentFieldDescription()) {
1349
            return $this;
1350
        }
1351
1352
        return $this->getParentFieldDescription()->getAdmin()->getRoot();
1353
    }
1354
1355
    public function setBaseControllerName(string $baseControllerName): void
1356
    {
1357
        $this->baseControllerName = $baseControllerName;
1358
    }
1359
1360
    public function getBaseControllerName(): string
1361
    {
1362
        return $this->baseControllerName;
1363
    }
1364
1365
    public function setLabel(?string $label): void
1366
    {
1367
        $this->label = $label;
1368
    }
1369
1370
    public function getLabel(): ?string
1371
    {
1372
        return $this->label;
1373
    }
1374
1375
    /**
1376
     * @param bool $persist
1377
     *
1378
     * NEXT_MAJOR: remove this method
1379
     *
1380
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
1381
     */
1382
    public function setPersistFilters(bool $persist): void
1383
    {
1384
        @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...
1385
            'The %s method is deprecated since version 3.34 and will be removed in 4.0.',
1386
            __METHOD__
1387
        ), E_USER_DEPRECATED);
1388
1389
        $this->persistFilters = $persist;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$persistFilters has been deprecated with message: since sonata-project/admin-bundle 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...
1390
    }
1391
1392
    public function setFilterPersister(?FilterPersisterInterface $filterPersister = null): void
1393
    {
1394
        $this->filterPersister = $filterPersister;
1395
        // NEXT_MAJOR remove the deprecated property will be removed. Needed for persisted filter condition.
1396
        $this->persistFilters = true;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$persistFilters has been deprecated with message: since sonata-project/admin-bundle 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...
1397
    }
1398
1399
    /**
1400
     * NEXT_MAJOR: Remove this method.
1401
     *
1402
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
1403
     */
1404
    public function setMaxPerPage(int $maxPerPage): void
1405
    {
1406
        @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...
1407
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
1408
            __METHOD__
1409
        ), E_USER_DEPRECATED);
1410
1411
        $this->maxPerPage = $maxPerPage;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$maxPerPage has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
1412
    }
1413
1414
    public function getMaxPerPage(): int
1415
    {
1416
        // NEXT_MAJOR: Remove this line and uncomment the following.
1417
        return $this->maxPerPage;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$maxPerPage has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
1418
        // $sortValues = $this->getModelManager()->getDefaultSortValues($this->class);
1419
1420
        // return $sortValues['_per_page'] ?? 25;
1421
    }
1422
1423
    public function setMaxPageLinks(int $maxPageLinks): void
1424
    {
1425
        $this->maxPageLinks = $maxPageLinks;
1426
    }
1427
1428
    public function getMaxPageLinks(): int
1429
    {
1430
        return $this->maxPageLinks;
1431
    }
1432
1433
    public function getFormGroups(): array
1434
    {
1435
        return $this->formGroups;
1436
    }
1437
1438
    public function setFormGroups(array $formGroups): void
1439
    {
1440
        $this->formGroups = $formGroups;
1441
    }
1442
1443
    public function removeFieldFromFormGroup(string $key): void
1444
    {
1445
        foreach ($this->formGroups as $name => $formGroup) {
1446
            unset($this->formGroups[$name]['fields'][$key]);
1447
1448
            if (empty($this->formGroups[$name]['fields'])) {
1449
                unset($this->formGroups[$name]);
1450
            }
1451
        }
1452
    }
1453
1454
    public function reorderFormGroup(string $group, array $keys): void
1455
    {
1456
        $formGroups = $this->getFormGroups();
1457
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1458
        $this->setFormGroups($formGroups);
1459
    }
1460
1461
    public function getFormTabs(): array
1462
    {
1463
        return $this->formTabs;
1464
    }
1465
1466
    public function setFormTabs(array $formTabs): void
1467
    {
1468
        $this->formTabs = $formTabs;
1469
    }
1470
1471
    public function getShowTabs(): array
1472
    {
1473
        return $this->showTabs;
1474
    }
1475
1476
    public function setShowTabs(array $showTabs): void
1477
    {
1478
        $this->showTabs = $showTabs;
1479
    }
1480
1481
    public function getShowGroups(): array
1482
    {
1483
        return $this->showGroups;
1484
    }
1485
1486
    public function setShowGroups(array $showGroups): void
1487
    {
1488
        $this->showGroups = $showGroups;
1489
    }
1490
1491
    public function reorderShowGroup(string $group, array $keys): void
1492
    {
1493
        $showGroups = $this->getShowGroups();
1494
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1495
        $this->setShowGroups($showGroups);
1496
    }
1497
1498
    public function setParentFieldDescription(?FieldDescriptionInterface $parentFieldDescription): void
1499
    {
1500
        $this->parentFieldDescription = $parentFieldDescription;
1501
    }
1502
1503
    public function getParentFieldDescription(): ?FieldDescriptionInterface
1504
    {
1505
        if (!$this->hasParentFieldDescription()) {
1506
            @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...
1507
                'Calling %s() when there is no parent field description is deprecated since'
1508
                .' sonata-project/admin-bundle 3.66 and will throw an exception in 4.0.'
1509
                .' Use %s::hasParentFieldDescription() to know if there is a parent field description.',
1510
                __METHOD__,
1511
                __CLASS__
1512
            ), E_USER_DEPRECATED);
1513
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1514
            // throw new \LogicException(sprintf(
1515
            //    'Admin "%s" has no parent field description.',
1516
            //    static::class
1517
            // ));
1518
1519
            return null;
1520
        }
1521
1522
        return $this->parentFieldDescription;
1523
    }
1524
1525
    public function hasParentFieldDescription(): bool
1526
    {
1527
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1528
    }
1529
1530
    public function setSubject(?object $subject): void
1531
    {
1532
        if (\is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1533
            $message = <<<'EOT'
1534
You are trying to set entity an instance of "%s",
1535
which is not the one registered with this admin class ("%s").
1536
This is deprecated since 3.5 and will no longer be supported in 4.0.
1537
EOT;
1538
1539
            // NEXT_MAJOR : throw an exception instead
1540
            @trigger_error(sprintf($message, \get_class($subject), $this->getClass()), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1541
        }
1542
1543
        $this->subject = $subject;
1544
    }
1545
1546
    public function getSubject(): ?object
1547
    {
1548
        if (!$this->hasSubject()) {
1549
            @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...
1550
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.66'
1551
                .' and will throw an exception in 4.0. Use %s::hasSubject() to know if there is a subject.',
1552
                __METHOD__,
1553
                __CLASS__
1554
            ), E_USER_DEPRECATED);
1555
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and update the return type
1556
            // throw new \LogicException(sprintf(
1557
            //    'Admin "%s" has no subject.',
1558
            //    static::class
1559
            // ));
1560
1561
            return null;
1562
        }
1563
1564
        return $this->subject;
1565
    }
1566
1567
    public function hasSubject(): bool
1568
    {
1569
        if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
1570
            $id = $this->request->get($this->getIdParameter());
1571
1572
            if (null !== $id) {
1573
                $this->subject = $this->getObject($id);
1574
            }
1575
        }
1576
1577
        return null !== $this->subject;
1578
    }
1579
1580
    public function getFormFieldDescriptions(): array
1581
    {
1582
        $this->buildForm();
1583
1584
        return $this->formFieldDescriptions;
1585
    }
1586
1587
    public function getFormFieldDescription(string $name): ?FieldDescriptionInterface
1588
    {
1589
        $this->buildForm();
1590
1591
        if (!$this->hasFormFieldDescription($name)) {
1592
            @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...
1593
                'Calling %s() when there is no form field description is deprecated since'
1594
                .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
1595
                .' Use %s::hasFormFieldDescription() to know if there is a form field description.',
1596
                __METHOD__,
1597
                __CLASS__
1598
            ), E_USER_DEPRECATED);
1599
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1600
            // throw new \LogicException(sprintf(
1601
            //    'Admin "%s" has no form field description for the field %s.',
1602
            //    static::class,
1603
            //    $name
1604
            // ));
1605
1606
            return null;
1607
        }
1608
1609
        return $this->formFieldDescriptions[$name];
1610
    }
1611
1612
    /**
1613
     * Returns true if the admin has a FieldDescription with the given $name.
1614
     */
1615
    public function hasFormFieldDescription(string $name): bool
1616
    {
1617
        $this->buildForm();
1618
1619
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1620
    }
1621
1622
    public function addFormFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1623
    {
1624
        $this->formFieldDescriptions[$name] = $fieldDescription;
1625
    }
1626
1627
    /**
1628
     * remove a FieldDescription.
1629
     */
1630
    public function removeFormFieldDescription(string $name): void
1631
    {
1632
        unset($this->formFieldDescriptions[$name]);
1633
    }
1634
1635
    /**
1636
     * build and return the collection of form FieldDescription.
1637
     *
1638
     * @return FieldDescriptionInterface[] collection of form FieldDescription
1639
     */
1640
    public function getShowFieldDescriptions(): array
1641
    {
1642
        $this->buildShow();
1643
1644
        return $this->showFieldDescriptions;
1645
    }
1646
1647
    /**
1648
     * Returns the form FieldDescription with the given $name.
1649
     */
1650
    public function getShowFieldDescription(string $name): ?FieldDescriptionInterface
1651
    {
1652
        $this->buildShow();
1653
1654
        if (!$this->hasShowFieldDescription($name)) {
1655
            @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...
1656
                'Calling %s() when there is no show field description is deprecated since'
1657
                .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
1658
                .' Use %s::hasFormFieldDescription() to know if there is a show field description.',
1659
                __METHOD__,
1660
                __CLASS__
1661
            ), E_USER_DEPRECATED);
1662
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1663
            // throw new \LogicException(sprintf(
1664
            //    'Admin "%s" has no show field description for the field %s.',
1665
            //    static::class,
1666
            //    $name
1667
            // ));
1668
1669
            return null;
1670
        }
1671
1672
        return $this->showFieldDescriptions[$name];
1673
    }
1674
1675
    public function hasShowFieldDescription(string $name): bool
1676
    {
1677
        $this->buildShow();
1678
1679
        return \array_key_exists($name, $this->showFieldDescriptions);
1680
    }
1681
1682
    public function addShowFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1683
    {
1684
        $this->showFieldDescriptions[$name] = $fieldDescription;
1685
    }
1686
1687
    public function removeShowFieldDescription(string $name): void
1688
    {
1689
        unset($this->showFieldDescriptions[$name]);
1690
    }
1691
1692
    public function getListFieldDescriptions(): array
1693
    {
1694
        $this->buildList();
1695
1696
        return $this->listFieldDescriptions;
1697
    }
1698
1699
    public function getListFieldDescription(string $name): ?FieldDescriptionInterface
1700
    {
1701
        $this->buildList();
1702
1703
        if (!$this->hasListFieldDescription($name)) {
1704
            @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...
1705
                'Calling %s() when there is no list field description is deprecated since'
1706
                .' sonata-project/admin-bundle 3.66 and will throw an exception in 4.0.'
1707
                .' Use %s::hasListFieldDescription(\'%s\') to know if there is a list field description.',
1708
                __METHOD__,
1709
                __CLASS__,
1710
                $name
1711
            ), E_USER_DEPRECATED);
1712
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1713
            // throw new \LogicException(sprintf(
1714
            //    'Admin "%s" has no list field description for %s.',
1715
            //    static::class,
1716
            //    $name
1717
            // ));
1718
1719
            return null;
1720
        }
1721
1722
        return $this->listFieldDescriptions[$name];
1723
    }
1724
1725
    public function hasListFieldDescription(string $name): bool
1726
    {
1727
        $this->buildList();
1728
1729
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1730
    }
1731
1732
    public function addListFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1733
    {
1734
        $this->listFieldDescriptions[$name] = $fieldDescription;
1735
    }
1736
1737
    public function removeListFieldDescription(string $name): void
1738
    {
1739
        unset($this->listFieldDescriptions[$name]);
1740
    }
1741
1742
    public function getFilterFieldDescription(string $name): ?FieldDescriptionInterface
1743
    {
1744
        $this->buildDatagrid();
1745
1746
        if (!$this->hasFilterFieldDescription($name)) {
1747
            @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...
1748
                'Calling %s() when there is no filter field description is deprecated since'
1749
                .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
1750
                .' Use %s::hasFilterFieldDescription() to know if there is a filter field description.',
1751
                __METHOD__,
1752
                __CLASS__
1753
            ), E_USER_DEPRECATED);
1754
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1755
            // throw new \LogicException(sprintf(
1756
            //    'Admin "%s" has no filter field description for the field %s.',
1757
            //    static::class,
1758
            //    $name
1759
            // ));
1760
1761
            return null;
1762
        }
1763
1764
        return $this->filterFieldDescriptions[$name];
1765
    }
1766
1767
    public function hasFilterFieldDescription(string $name): bool
1768
    {
1769
        $this->buildDatagrid();
1770
1771
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1772
    }
1773
1774
    public function addFilterFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1775
    {
1776
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1777
    }
1778
1779
    public function removeFilterFieldDescription(string $name): void
1780
    {
1781
        unset($this->filterFieldDescriptions[$name]);
1782
    }
1783
1784
    public function getFilterFieldDescriptions(): array
1785
    {
1786
        $this->buildDatagrid();
1787
1788
        return $this->filterFieldDescriptions;
1789
    }
1790
1791
    public function addChild(AdminInterface $child): void
1792
    {
1793
        $parentAdmin = $this;
1794
        while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
1795
            $parentAdmin = $parentAdmin->getParent();
1796
        }
1797
1798
        if ($parentAdmin->getCode() === $child->getCode()) {
1799
            throw new \RuntimeException(sprintf(
1800
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1801
                $child->getCode(),
1802
                $this->getCode()
1803
            ));
1804
        }
1805
1806
        $this->children[$child->getCode()] = $child;
1807
1808
        $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...
1809
1810
        // NEXT_MAJOR: remove $args and add $field parameter to this function on next Major
1811
1812
        $args = \func_get_args();
1813
1814
        if (isset($args[1])) {
1815
            $child->addParentAssociationMapping($this->getCode(), $args[1]);
1816
        } else {
1817
            @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...
1818
                'Calling "addChild" without second argument is deprecated since sonata-project/admin-bundle 3.35 and will not be allowed in 4.0.',
1819
                E_USER_DEPRECATED
1820
            );
1821
        }
1822
    }
1823
1824
    public function hasChild(string $code): bool
1825
    {
1826
        return isset($this->children[$code]);
1827
    }
1828
1829
    public function getChildren(): array
1830
    {
1831
        return $this->children;
1832
    }
1833
1834
    public function getChild(string $code): ?AdminInterface
1835
    {
1836
        if (!$this->hasChild($code)) {
1837
            @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...
1838
                'Calling %s() when there is no child is deprecated since sonata-project/admin-bundle 3.69'
1839
                .' and will throw an exception in 4.0. Use %s::hasChild() to know if the child exists.',
1840
                __METHOD__,
1841
                __CLASS__
1842
            ), E_USER_DEPRECATED);
1843
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
1844
            // throw new \LogicException(sprintf(
1845
            //    'Admin "%s" has no child for the code %s.',
1846
            //    static::class,
1847
            //    $code
1848
            // ));
1849
1850
            return null;
1851
        }
1852
1853
        return $this->children[$code];
1854
    }
1855
1856
    public function setParent(AdminInterface $parent): void
1857
    {
1858
        $this->parent = $parent;
1859
    }
1860
1861
    public function getParent(): ?AdminInterface
1862
    {
1863
        if (!$this->isChild()) {
1864
            @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...
1865
                'Calling %s() when there is no parent is deprecated since sonata-project/admin-bundle 3.66'
1866
                .' and will throw an exception in 4.0. Use %s::isChild() to know if there is a parent.',
1867
                __METHOD__,
1868
                __CLASS__
1869
            ), E_USER_DEPRECATED);
1870
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
1871
            // throw new \LogicException(sprintf(
1872
            //    'Admin "%s" has no parent.',
1873
            //    static::class
1874
            // ));
1875
1876
            return null;
1877
        }
1878
1879
        return $this->parent;
1880
    }
1881
1882
    final public function getRootAncestor(): AdminInterface
1883
    {
1884
        $parent = $this;
1885
1886
        while ($parent->isChild()) {
1887
            $parent = $parent->getParent();
1888
        }
1889
1890
        return $parent;
1891
    }
1892
1893
    final public function getChildDepth(): int
1894
    {
1895
        $parent = $this;
1896
        $depth = 0;
1897
1898
        while ($parent->isChild()) {
1899
            $parent = $parent->getParent();
1900
            ++$depth;
1901
        }
1902
1903
        return $depth;
1904
    }
1905
1906
    final public function getCurrentLeafChildAdmin(): ?AdminInterface
1907
    {
1908
        $child = $this->getCurrentChildAdmin();
1909
1910
        if (null === $child) {
1911
            return null;
1912
        }
1913
1914
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1915
            $child = $c;
1916
        }
1917
1918
        return $child;
1919
    }
1920
1921
    public function isChild(): bool
1922
    {
1923
        return $this->parent instanceof AdminInterface;
1924
    }
1925
1926
    /**
1927
     * Returns true if the admin has children, false otherwise.
1928
     */
1929
    public function hasChildren(): bool
1930
    {
1931
        return \count($this->children) > 0;
1932
    }
1933
1934
    public function setUniqid(string $uniqid): void
1935
    {
1936
        $this->uniqid = $uniqid;
1937
    }
1938
1939
    public function getUniqid(): string
1940
    {
1941
        if (!$this->uniqid) {
1942
            $this->uniqid = sprintf('s%s', uniqid());
1943
        }
1944
1945
        return $this->uniqid;
1946
    }
1947
1948
    /**
1949
     * {@inheritdoc}
1950
     */
1951
    public function getClassnameLabel(): string
1952
    {
1953
        return $this->classnameLabel;
1954
    }
1955
1956
    public function getPersistentParameters(): array
1957
    {
1958
        $parameters = [];
1959
1960
        foreach ($this->getExtensions() as $extension) {
1961
            $params = $extension->getPersistentParameters($this);
1962
1963
            $parameters = array_merge($parameters, $params);
1964
        }
1965
1966
        return $parameters;
1967
    }
1968
1969
    /**
1970
     * {@inheritdoc}
1971
     */
1972
    public function getPersistentParameter(string $name)
1973
    {
1974
        $parameters = $this->getPersistentParameters();
1975
1976
        return $parameters[$name] ?? null;
1977
    }
1978
1979
    public function setCurrentChild(bool $currentChild): void
1980
    {
1981
        $this->currentChild = $currentChild;
1982
    }
1983
1984
    /**
1985
     * NEXT_MAJOR: Remove this method.
1986
     *
1987
     * @deprecated since sonata-project/admin-bundle 3.65, to be removed in 4.0
1988
     */
1989
    public function getCurrentChild(): bool
1990
    {
1991
        @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...
1992
            'The %s() method is deprecated since version 3.65 and will be removed in 4.0.'
1993
            .' Use %s::isCurrentChild() instead.',
1994
            __METHOD__,
1995
            __CLASS__
1996
        ), E_USER_DEPRECATED);
1997
1998
        return $this->currentChild;
1999
    }
2000
2001
    public function isCurrentChild(): bool
2002
    {
2003
        return $this->currentChild;
2004
    }
2005
2006
    /**
2007
     * Returns the current child admin instance.
2008
     *
2009
     * @return AdminInterface|null the current child admin instance
2010
     */
2011
    public function getCurrentChildAdmin(): ?AdminInterface
2012
    {
2013
        foreach ($this->children as $children) {
2014
            if ($children->isCurrentChild()) {
2015
                return $children;
2016
            }
2017
        }
2018
2019
        return null;
2020
    }
2021
2022
    public function trans($id, array $parameters = [], $domain = null, $locale = null): string
2023
    {
2024
        @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...
2025
            'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2026
            __METHOD__
2027
        ), E_USER_DEPRECATED);
2028
2029
        $domain = $domain ?: $this->getTranslationDomain();
2030
2031
        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 sonata-project/admin-bundle 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...
2032
    }
2033
2034
    public function setTranslationDomain(string $translationDomain): void
2035
    {
2036
        $this->translationDomain = $translationDomain;
2037
    }
2038
2039
    public function getTranslationDomain(): string
2040
    {
2041
        return $this->translationDomain;
2042
    }
2043
2044
    /**
2045
     * {@inheritdoc}
2046
     *
2047
     * NEXT_MAJOR: remove this method
2048
     *
2049
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2050
     */
2051
    public function setTranslator(?TranslatorInterface $translator): void
2052
    {
2053
        $args = \func_get_args();
2054
        if (isset($args[1]) && $args[1]) {
2055
            @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...
2056
                'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2057
                __METHOD__
2058
            ), E_USER_DEPRECATED);
2059
        }
2060
2061
        $this->translator = $translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since sonata-project/admin-bundle 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...
2062
    }
2063
2064
    /**
2065
     * {@inheritdoc}
2066
     *
2067
     * NEXT_MAJOR: remove this method
2068
     *
2069
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2070
     */
2071
    public function getTranslator(): ?TranslatorInterface
2072
    {
2073
        @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...
2074
            'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2075
            __METHOD__
2076
        ), E_USER_DEPRECATED);
2077
2078
        return $this->translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since sonata-project/admin-bundle 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...
2079
    }
2080
2081
    public function getTranslationLabel(string $label, string $context = '', string $type = ''): string
2082
    {
2083
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2084
    }
2085
2086
    public function setRequest(Request $request): void
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...
2087
    {
2088
        $this->request = $request;
2089
2090
        foreach ($this->getChildren() as $children) {
2091
            $children->setRequest($request);
2092
        }
2093
    }
2094
2095
    public function getRequest(): Request
2096
    {
2097
        if (!$this->request) {
2098
            // NEXT_MAJOR: Throw \LogicException instead.
2099
            throw new \RuntimeException('The Request object has not been set');
2100
        }
2101
2102
        return $this->request;
2103
    }
2104
2105
    public function hasRequest(): bool
2106
    {
2107
        return null !== $this->request;
2108
    }
2109
2110
    public function setFormContractor(?FormContractorInterface $formBuilder): void
2111
    {
2112
        $this->formContractor = $formBuilder;
2113
    }
2114
2115
    public function getFormContractor(): ?FormContractorInterface
2116
    {
2117
        return $this->formContractor;
2118
    }
2119
2120
    public function setDatagridBuilder(?DatagridBuilderInterface $datagridBuilder): void
2121
    {
2122
        $this->datagridBuilder = $datagridBuilder;
2123
    }
2124
2125
    public function getDatagridBuilder(): ?DatagridBuilderInterface
2126
    {
2127
        return $this->datagridBuilder;
2128
    }
2129
2130
    public function setListBuilder(?ListBuilderInterface $listBuilder): void
2131
    {
2132
        $this->listBuilder = $listBuilder;
2133
    }
2134
2135
    public function getListBuilder(): ?ListBuilderInterface
2136
    {
2137
        return $this->listBuilder;
2138
    }
2139
2140
    public function setShowBuilder(?ShowBuilderInterface $showBuilder): void
2141
    {
2142
        $this->showBuilder = $showBuilder;
2143
    }
2144
2145
    public function getShowBuilder(): ?ShowBuilderInterface
2146
    {
2147
        return $this->showBuilder;
2148
    }
2149
2150
    public function setConfigurationPool(?Pool $configurationPool): void
2151
    {
2152
        $this->configurationPool = $configurationPool;
2153
    }
2154
2155
    public function getConfigurationPool(): ?Pool
2156
    {
2157
        return $this->configurationPool;
2158
    }
2159
2160
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator): void
2161
    {
2162
        $this->routeGenerator = $routeGenerator;
2163
    }
2164
2165
    public function getRouteGenerator(): ?RouteGeneratorInterface
2166
    {
2167
        return $this->routeGenerator;
2168
    }
2169
2170
    public function getCode(): string
2171
    {
2172
        return $this->code;
2173
    }
2174
2175
    public function getBaseCodeRoute(): string
2176
    {
2177
        if ($this->isChild()) {
2178
            return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2179
        }
2180
2181
        return $this->getCode();
2182
    }
2183
2184
    public function getModelManager(): ?ModelManagerInterface
2185
    {
2186
        return $this->modelManager;
2187
    }
2188
2189
    public function setModelManager(?ModelManagerInterface $modelManager): void
2190
    {
2191
        $this->modelManager = $modelManager;
2192
    }
2193
2194
    public function getManagerType(): ?string
2195
    {
2196
        return $this->managerType;
2197
    }
2198
2199
    public function setManagerType(?string $type): void
2200
    {
2201
        $this->managerType = $type;
2202
    }
2203
2204
    public function getObjectIdentifier()
2205
    {
2206
        return $this->getCode();
2207
    }
2208
2209
    /**
2210
     * Set the roles and permissions per role.
2211
     */
2212
    public function setSecurityInformation(array $information): void
2213
    {
2214
        $this->securityInformation = $information;
2215
    }
2216
2217
    public function getSecurityInformation(): array
2218
    {
2219
        return $this->securityInformation;
2220
    }
2221
2222
    /**
2223
     * Return the list of permissions the user should have in order to display the admin.
2224
     */
2225
    public function getPermissionsShow(string $context): array
2226
    {
2227
        switch ($context) {
2228
            case self::CONTEXT_DASHBOARD:
2229
            case self::CONTEXT_MENU:
2230
            default:
2231
                return ['LIST'];
2232
        }
2233
    }
2234
2235
    public function showIn(string $context): bool
2236
    {
2237
        switch ($context) {
2238
            case self::CONTEXT_DASHBOARD:
2239
            case self::CONTEXT_MENU:
2240
            default:
2241
                return $this->isGranted($this->getPermissionsShow($context));
0 ignored issues
show
Documentation introduced by
$this->getPermissionsShow($context) 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...
2242
        }
2243
    }
2244
2245
    public function createObjectSecurity(object $object): void
2246
    {
2247
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2248
    }
2249
2250
    public function setSecurityHandler(?SecurityHandlerInterface $securityHandler): void
2251
    {
2252
        $this->securityHandler = $securityHandler;
2253
    }
2254
2255
    public function getSecurityHandler(): ?SecurityHandlerInterface
2256
    {
2257
        return $this->securityHandler;
2258
    }
2259
2260
    /**
2261
     * NEXT_MAJOR: Decide the type declaration for the $name argument, since it is
2262
     * passed as argument 1 for `SecurityHandlerInterface::isGranted()`, which
2263
     * accepts string and array.
2264
     */
2265
    public function isGranted($name, ?object $object = null): bool
2266
    {
2267
        $objectRef = $object ? sprintf('/%s#%s', spl_object_hash($object), $this->id($object)) : '';
2268
        $key = md5(json_encode($name).$objectRef);
2269
2270
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
2271
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2272
        }
2273
2274
        return $this->cacheIsGranted[$key];
2275
    }
2276
2277
    /**
2278
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2279
     * passed as argument 1 for `ModelManagerInterface::getUrlSafeIdentifier()`, which
2280
     * accepts null.
2281
     */
2282
    public function getUrlSafeIdentifier($model): ?string
2283
    {
2284
        return $this->getModelManager()->getUrlSafeIdentifier($model);
2285
    }
2286
2287
    /**
2288
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2289
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
2290
     * accepts null.
2291
     */
2292
    public function getNormalizedIdentifier($model): ?string
2293
    {
2294
        return $this->getModelManager()->getNormalizedIdentifier($model);
2295
    }
2296
2297
    /**
2298
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2299
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
2300
     * accepts null.
2301
     */
2302
    public function id($model): ?string
2303
    {
2304
        return $this->getNormalizedIdentifier($model);
2305
    }
2306
2307
    public function setValidator(?ValidatorInterface $validator): void
2308
    {
2309
        $this->validator = $validator;
2310
    }
2311
2312
    public function getValidator(): ?ValidatorInterface
2313
    {
2314
        return $this->validator;
2315
    }
2316
2317
    public function getShow(): ?FieldDescriptionCollection
2318
    {
2319
        $this->buildShow();
2320
2321
        return $this->show;
2322
    }
2323
2324
    public function setFormTheme(array $formTheme): void
2325
    {
2326
        $this->formTheme = $formTheme;
2327
    }
2328
2329
    public function getFormTheme(): array
2330
    {
2331
        return $this->formTheme;
2332
    }
2333
2334
    public function setFilterTheme(array $filterTheme): void
2335
    {
2336
        $this->filterTheme = $filterTheme;
2337
    }
2338
2339
    public function getFilterTheme(): array
2340
    {
2341
        return $this->filterTheme;
2342
    }
2343
2344
    public function addExtension(AdminExtensionInterface $extension): void
2345
    {
2346
        $this->extensions[] = $extension;
2347
    }
2348
2349
    public function getExtensions(): array
2350
    {
2351
        return $this->extensions;
2352
    }
2353
2354
    public function setMenuFactory(?FactoryInterface $menuFactory): void
2355
    {
2356
        $this->menuFactory = $menuFactory;
2357
    }
2358
2359
    public function getMenuFactory(): ?FactoryInterface
2360
    {
2361
        return $this->menuFactory;
2362
    }
2363
2364
    public function setRouteBuilder(?RouteBuilderInterface $routeBuilder): void
2365
    {
2366
        $this->routeBuilder = $routeBuilder;
2367
    }
2368
2369
    public function getRouteBuilder(): ?RouteBuilderInterface
2370
    {
2371
        return $this->routeBuilder;
2372
    }
2373
2374
    /**
2375
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since there
2376
     * are tests ensuring to accept null (`GetShortObjectDescriptionActionTest::testGetShortObjectDescriptionActionEmptyObjectIdAsJson()`).
2377
     */
2378
    public function toString($object): string
2379
    {
2380
        if (!\is_object($object)) {
2381
            return '';
2382
        }
2383
2384
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2385
            return (string) $object;
2386
        }
2387
2388
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2389
    }
2390
2391
    public function setLabelTranslatorStrategy(?LabelTranslatorStrategyInterface $labelTranslatorStrategy): void
2392
    {
2393
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2394
    }
2395
2396
    public function getLabelTranslatorStrategy(): ?LabelTranslatorStrategyInterface
2397
    {
2398
        return $this->labelTranslatorStrategy;
2399
    }
2400
2401
    public function supportsPreviewMode(): bool
2402
    {
2403
        return $this->supportsPreviewMode;
2404
    }
2405
2406
    /**
2407
     * NEXT_MAJOR: Remove this.
2408
     *
2409
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
2410
     *
2411
     * Set custom per page options.
2412
     */
2413
    public function setPerPageOptions(array $options): void
2414
    {
2415
        @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...
2416
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
2417
            __METHOD__
2418
        ), E_USER_DEPRECATED);
2419
2420
        $this->perPageOptions = $options;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$perPageOptions has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
2421
    }
2422
2423
    /**
2424
     * Returns predefined per page options.
2425
     */
2426
    public function getPerPageOptions(): array
2427
    {
2428
        // NEXT_MAJOR: Remove this line and uncomment the following
2429
        return $this->perPageOptions;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$perPageOptions has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
2430
//        $perPageOptions = $this->getModelManager()->getDefaultPerPageOptions($this->class);
2431
//        $perPageOptions[] = $this->getMaxPerPage();
2432
//
2433
//        $perPageOptions = array_unique($perPageOptions);
2434
//        sort($perPageOptions);
2435
//
2436
//        return $perPageOptions;
2437
    }
2438
2439
    /**
2440
     * Set pager type.
2441
     */
2442
    public function setPagerType(string $pagerType): void
2443
    {
2444
        $this->pagerType = $pagerType;
2445
    }
2446
2447
    /**
2448
     * Get pager type.
2449
     */
2450
    public function getPagerType(): string
2451
    {
2452
        return $this->pagerType;
2453
    }
2454
2455
    /**
2456
     * Returns true if the per page value is allowed, false otherwise.
2457
     */
2458
    public function determinedPerPageValue(int $perPage): bool
2459
    {
2460
        return \in_array($perPage, $this->getPerPageOptions(), true);
2461
    }
2462
2463
    public function isAclEnabled(): bool
2464
    {
2465
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2466
    }
2467
2468
    /**
2469
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since it is
2470
     * passed as argument 1 to `toString()` method, which currently accepts null.
2471
     */
2472
    public function getObjectMetadata($object): MetadataInterface
2473
    {
2474
        return new Metadata($this->toString($object));
2475
    }
2476
2477
    public function getListModes(): array
2478
    {
2479
        return $this->listModes;
2480
    }
2481
2482
    public function setListMode(string $mode): void
2483
    {
2484
        if (!$this->hasRequest()) {
2485
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2486
        }
2487
2488
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2489
    }
2490
2491
    public function getListMode(): string
2492
    {
2493
        if (!$this->hasRequest()) {
2494
            return 'list';
2495
        }
2496
2497
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2498
    }
2499
2500
    public function getAccessMapping(): array
2501
    {
2502
        return $this->accessMapping;
2503
    }
2504
2505
    public function checkAccess(string $action, ?object $object = null): void
2506
    {
2507
        $access = $this->getAccess();
2508
2509
        if (!\array_key_exists($action, $access)) {
2510
            throw new \InvalidArgumentException(sprintf(
2511
                'Action "%s" could not be found in access mapping.'
2512
                .' Please make sure your action is defined into your admin class accessMapping property.',
2513
                $action
2514
            ));
2515
        }
2516
2517
        if (!\is_array($access[$action])) {
2518
            $access[$action] = [$access[$action]];
2519
        }
2520
2521
        foreach ($access[$action] as $role) {
2522
            if (false === $this->isGranted($role, $object)) {
2523
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2524
            }
2525
        }
2526
    }
2527
2528
    /**
2529
     * {@inheritdoc}
2530
     */
2531
    public function hasAccess(string $action, ?object $object = null): bool
2532
    {
2533
        $access = $this->getAccess();
2534
2535
        if (!\array_key_exists($action, $access)) {
2536
            return false;
2537
        }
2538
2539
        if (!\is_array($access[$action])) {
2540
            $access[$action] = [$access[$action]];
2541
        }
2542
2543
        foreach ($access[$action] as $role) {
2544
            if (false === $this->isGranted($role, $object)) {
2545
                return false;
2546
            }
2547
        }
2548
2549
        return true;
2550
    }
2551
2552
    final public function getActionButtons(string $action, ?object $object = null): array
2553
    {
2554
        $buttonList = [];
2555
2556
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
2557
            && $this->hasAccess('create')
2558
            && $this->hasRoute('create')
2559
        ) {
2560
            $buttonList['create'] = [
2561
                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2562
            ];
2563
        }
2564
2565
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
2566
            && $this->canAccessObject('edit', $object)
2567
            && $this->hasRoute('edit')
2568
        ) {
2569
            $buttonList['edit'] = [
2570
                'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2571
            ];
2572
        }
2573
2574
        if (\in_array($action, ['show', 'edit', 'acl'], true)
2575
            && $this->canAccessObject('history', $object)
2576
            && $this->hasRoute('history')
2577
        ) {
2578
            $buttonList['history'] = [
2579
                'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2580
            ];
2581
        }
2582
2583
        if (\in_array($action, ['edit', 'history'], true)
2584
            && $this->isAclEnabled()
2585
            && $this->canAccessObject('acl', $object)
2586
            && $this->hasRoute('acl')
2587
        ) {
2588
            $buttonList['acl'] = [
2589
                'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2590
            ];
2591
        }
2592
2593
        if (\in_array($action, ['edit', 'history', 'acl'], true)
2594
            && $this->canAccessObject('show', $object)
2595
            && \count($this->getShow()) > 0
2596
            && $this->hasRoute('show')
2597
        ) {
2598
            $buttonList['show'] = [
2599
                'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2600
            ];
2601
        }
2602
2603
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
2604
            && $this->hasAccess('list')
2605
            && $this->hasRoute('list')
2606
        ) {
2607
            $buttonList['list'] = [
2608
                'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2609
            ];
2610
        }
2611
2612
        $buttonList = $this->configureActionButtons($buttonList, $action, $object);
2613
2614
        foreach ($this->getExtensions() as $extension) {
2615
            $buttonList = $extension->configureActionButtons($this, $buttonList, $action, $object);
2616
        }
2617
2618
        return $buttonList;
2619
    }
2620
2621
    /**
2622
     * {@inheritdoc}
2623
     */
2624
    public function getDashboardActions(): array
2625
    {
2626
        $actions = [];
2627
2628
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2629
            $actions['create'] = [
2630
                'label' => 'link_add',
2631
                'translation_domain' => 'SonataAdminBundle',
2632
                'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2633
                'url' => $this->generateUrl('create'),
2634
                'icon' => 'plus-circle',
2635
            ];
2636
        }
2637
2638
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2639
            $actions['list'] = [
2640
                'label' => 'link_list',
2641
                'translation_domain' => 'SonataAdminBundle',
2642
                'url' => $this->generateUrl('list'),
2643
                'icon' => 'list',
2644
            ];
2645
        }
2646
2647
        return $actions;
2648
    }
2649
2650
    /**
2651
     * {@inheritdoc}
2652
     */
2653
    final public function showMosaicButton($isShown): void
2654
    {
2655
        if ($isShown) {
2656
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
2657
        } else {
2658
            unset($this->listModes['mosaic']);
2659
        }
2660
    }
2661
2662
    final public function getSearchResultLink(object $object): ?string
2663
    {
2664
        foreach ($this->searchResultActions as $action) {
2665
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2666
                return $this->generateObjectUrl($action, $object);
2667
            }
2668
        }
2669
2670
        return null;
2671
    }
2672
2673
    /**
2674
     * Checks if a filter type is set to a default value.
2675
     */
2676
    final public function isDefaultFilter(string $name): bool
2677
    {
2678
        $filter = $this->getFilterParameters();
2679
        $default = $this->getDefaultFilterValues();
2680
2681
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
2682
            return false;
2683
        }
2684
2685
        return $filter[$name] === $default[$name];
2686
    }
2687
2688
    public function canAccessObject(string $action, ?object $object = null): bool
2689
    {
2690
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2691
    }
2692
2693
    public function configureActionButtons(array $buttonList, string $action, ?object $object = null): array
2694
    {
2695
        return $buttonList;
2696
    }
2697
2698
    /**
2699
     * Hook to run after initialization.
2700
     */
2701
    protected function configure(): void
2702
    {
2703
    }
2704
2705
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
2706
    {
2707
        return $query;
2708
    }
2709
2710
    /**
2711
     * urlize the given word.
2712
     *
2713
     * @param string $sep the separator
2714
     */
2715
    final protected function urlize(string $word, string $sep = '_'): string
2716
    {
2717
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
2718
    }
2719
2720
    final protected function getTemplateRegistry(): MutableTemplateRegistryInterface
2721
    {
2722
        return $this->templateRegistry;
2723
    }
2724
2725
    /**
2726
     * Returns a list of default sort values.
2727
     *
2728
     * @return array{_page?: int, _per_page?: int, _sort_by?: string, _sort_order?: string}
0 ignored issues
show
Documentation introduced by
The doc-type array{_page?: could not be parsed: Unknown type name "array{_page?:" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
2729
     */
2730
    final protected function getDefaultSortValues(): array
2731
    {
2732
        $defaultSortValues = [];
2733
2734
        $this->configureDefaultSortValues($defaultSortValues);
2735
2736
        foreach ($this->getExtensions() as $extension) {
2737
            // NEXT_MAJOR: remove method check
2738
            if (method_exists($extension, 'configureDefaultSortValues')) {
2739
                $extension->configureDefaultSortValues($this, $defaultSortValues);
2740
            }
2741
        }
2742
2743
        return $defaultSortValues;
2744
    }
2745
2746
    /**
2747
     * Returns a list of default filters.
2748
     */
2749
    final protected function getDefaultFilterValues(): array
2750
    {
2751
        $defaultFilterValues = [];
2752
2753
        $this->configureDefaultFilterValues($defaultFilterValues);
2754
2755
        foreach ($this->getExtensions() as $extension) {
2756
            // NEXT_MAJOR: remove method check
2757
            if (method_exists($extension, 'configureDefaultFilterValues')) {
2758
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2759
            }
2760
        }
2761
2762
        return $defaultFilterValues;
2763
    }
2764
2765
    protected function configureFormFields(FormMapper $form): void
2766
    {
2767
    }
2768
2769
    protected function configureListFields(ListMapper $list): void
2770
    {
2771
    }
2772
2773
    protected function configureDatagridFilters(DatagridMapper $filter): void
2774
    {
2775
    }
2776
2777
    protected function configureShowFields(ShowMapper $show): void
2778
    {
2779
    }
2780
2781
    protected function configureRoutes(RouteCollection $collection): void
2782
    {
2783
    }
2784
2785
    /**
2786
     * Allows you to customize batch actions.
2787
     *
2788
     * @param array $actions List of actions
2789
     */
2790
    protected function configureBatchActions(array $actions): array
2791
    {
2792
        return $actions;
2793
    }
2794
2795
    /**
2796
     * NEXT_MAJOR: remove this method.
2797
     *
2798
     * @deprecated Use configureTabMenu instead
2799
     */
2800
    protected function configureSideMenu(ItemInterface $menu, $action, ?AdminInterface $childAdmin = null): void
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...
2801
    {
2802
    }
2803
2804
    /**
2805
     * Configures the tab menu in your admin.
2806
     */
2807
    protected function configureTabMenu(ItemInterface $menu, string $action, ?AdminInterface $childAdmin = null): void
2808
    {
2809
        // Use configureSideMenu not to mess with previous overrides
2810
        // NEXT_MAJOR: remove this line
2811
        $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...
2812
    }
2813
2814
    /**
2815
     * build the view FieldDescription array.
2816
     */
2817
    protected function buildShow(): void
2818
    {
2819
        if ($this->loaded['show']) {
2820
            return;
2821
        }
2822
2823
        $this->loaded['show'] = true;
2824
2825
        $this->show = $this->getShowBuilder()->getBaseList();
2826
        $mapper = new ShowMapper($this->getShowBuilder(), $this->show, $this);
2827
2828
        $this->configureShowFields($mapper);
2829
2830
        foreach ($this->getExtensions() as $extension) {
2831
            $extension->configureShowFields($mapper);
2832
        }
2833
    }
2834
2835
    /**
2836
     * build the list FieldDescription array.
2837
     */
2838
    protected function buildList(): void
2839
    {
2840
        if ($this->loaded['list']) {
2841
            return;
2842
        }
2843
2844
        $this->loaded['list'] = true;
2845
2846
        $this->list = $this->getListBuilder()->getBaseList();
2847
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
2848
2849
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
2850
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2851
                $this->getClass(),
2852
                'batch',
2853
                [
2854
                    'label' => 'batch',
2855
                    'code' => '_batch',
2856
                    'sortable' => false,
2857
                    'virtual_field' => true,
2858
                ]
2859
            );
2860
2861
            $fieldDescription->setAdmin($this);
2862
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
2863
2864
            $mapper->add($fieldDescription, ListMapper::TYPE_BATCH);
2865
        }
2866
2867
        $this->configureListFields($mapper);
2868
2869
        foreach ($this->getExtensions() as $extension) {
2870
            $extension->configureListFields($mapper);
2871
        }
2872
2873
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
2874
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2875
                $this->getClass(),
2876
                'select',
2877
                [
2878
                    'label' => false,
2879
                    'code' => '_select',
2880
                    'sortable' => false,
2881
                    'virtual_field' => false,
2882
                ]
2883
            );
2884
2885
            $fieldDescription->setAdmin($this);
2886
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
2887
2888
            $mapper->add($fieldDescription, ListMapper::TYPE_SELECT);
2889
        }
2890
    }
2891
2892
    /**
2893
     * Build the form FieldDescription collection.
2894
     */
2895
    protected function buildForm(): void
2896
    {
2897
        if ($this->loaded['form']) {
2898
            return;
2899
        }
2900
2901
        $this->loaded['form'] = true;
2902
2903
        $formBuilder = $this->getFormBuilder();
2904
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
2905
            $this->preValidate($event->getData());
2906
        }, 100);
2907
2908
        $this->form = $formBuilder->getForm();
0 ignored issues
show
Documentation Bug introduced by
It seems like $formBuilder->getForm() of type object<Symfony\Component\Form\FormInterface> is incompatible with the declared type object<Symfony\Component\Form\Form>|null of property $form.

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...
2909
    }
2910
2911
    /**
2912
     * Gets the subclass corresponding to the given name.
2913
     *
2914
     * @param string $name The name of the sub class
2915
     *
2916
     * @return string the subclass
2917
     */
2918
    protected function getSubClass(string $name): string
2919
    {
2920
        if ($this->hasSubClass($name)) {
2921
            return $this->subClasses[$name];
2922
        }
2923
2924
        // NEXT_MAJOR: Throw \LogicException instead.
2925
        throw new \RuntimeException(sprintf('Unable to find the subclass `%s` for admin `%s`', $name, static::class));
2926
    }
2927
2928
    /**
2929
     * Attach the inline validator to the model metadata, this must be done once per admin.
2930
     */
2931
    protected function attachInlineValidator(): void
2932
    {
2933
        $admin = $this;
2934
2935
        // add the custom inline validation option
2936
        $metadata = $this->validator->getMetadataFor($this->getClass());
2937
        if (!$metadata instanceof GenericMetadata) {
2938
            throw new \UnexpectedValueException(
2939
                sprintf(
2940
                    'Cannot add inline validator for %s because its metadata is an instance of %s instead of %s',
2941
                    $this->getClass(),
2942
                    \get_class($metadata),
2943
                    GenericMetadata::class
2944
                )
2945
            );
2946
        }
2947
2948
        $metadata->addConstraint(new InlineConstraint([
2949
            'service' => $this,
2950
            'method' => static function (ErrorElement $errorElement, $object) use ($admin): void {
2951
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
2952
2953
                // This avoid the main validation to be cascaded to children
2954
                // The problem occurs when a model Page has a collection of Page as property
2955
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
2956
                    return;
2957
                }
2958
2959
                $admin->validate($errorElement, $object);
2960
2961
                foreach ($admin->getExtensions() as $extension) {
2962
                    $extension->validate($admin, $errorElement, $object);
2963
                }
2964
            },
2965
            'serializingWarning' => true,
2966
        ]));
2967
    }
2968
2969
    /**
2970
     * NEXT_MAJOR: Remove this function.
2971
     *
2972
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
2973
     *
2974
     * Predefine per page options.
2975
     */
2976
    protected function predefinePerPageOptions(): void
2977
    {
2978
        array_unshift($this->perPageOptions, $this->maxPerPage);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$perPageOptions has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$maxPerPage has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
2979
        $this->perPageOptions = array_unique($this->perPageOptions);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$perPageOptions has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
2980
        sort($this->perPageOptions);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$perPageOptions has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
2981
    }
2982
2983
    /**
2984
     * Return list routes with permissions name.
2985
     *
2986
     * @return array<string, string>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
2987
     */
2988
    protected function getAccess(): array
2989
    {
2990
        $access = array_merge([
2991
            'acl' => 'MASTER',
2992
            'export' => 'EXPORT',
2993
            'historyCompareRevisions' => 'EDIT',
2994
            'historyViewRevision' => 'EDIT',
2995
            'history' => 'EDIT',
2996
            'edit' => 'EDIT',
2997
            'show' => 'VIEW',
2998
            'create' => 'CREATE',
2999
            'delete' => 'DELETE',
3000
            'batchDelete' => 'DELETE',
3001
            'list' => 'LIST',
3002
        ], $this->getAccessMapping());
3003
3004
        foreach ($this->extensions as $extension) {
3005
            $access = array_merge($access, $extension->getAccessMapping($this));
3006
        }
3007
3008
        return $access;
3009
    }
3010
3011
    /**
3012
     * Configures a list of default filters.
3013
     */
3014
    protected function configureDefaultFilterValues(array &$filterValues): void
3015
    {
3016
    }
3017
3018
    /**
3019
     * Configures a list of default sort values.
3020
     *
3021
     * Example:
3022
     *   $sortValues['_sort_by'] = 'foo'
3023
     *   $sortValues['_sort_order'] = 'DESC'
3024
     */
3025
    protected function configureDefaultSortValues(array &$sortValues)
0 ignored issues
show
Unused Code introduced by
The parameter $sortValues 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...
3026
    {
3027
    }
3028
3029
    /**
3030
     * Set the parent object, if any, to the provided object.
3031
     */
3032
    final protected function appendParentObject(object $object): void
3033
    {
3034
        if ($this->isChild() && $this->getParentAssociationMapping()) {
3035
            $parentAdmin = $this->getParent();
3036
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
3037
3038
            if (null !== $parentObject) {
3039
                $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3040
                $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3041
3042
                $value = $propertyAccessor->getValue($object, $propertyPath);
3043
3044
                if (\is_array($value) || $value instanceof \ArrayAccess) {
3045
                    $value[] = $parentObject;
3046
                    $propertyAccessor->setValue($object, $propertyPath, $value);
3047
                } else {
3048
                    $propertyAccessor->setValue($object, $propertyPath, $parentObject);
3049
                }
3050
            }
3051
        } elseif ($this->hasParentFieldDescription()) {
3052
            $parentAdmin = $this->getParentFieldDescription()->getAdmin();
3053
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
3054
3055
            if (null !== $parentObject) {
3056
                ObjectManipulator::setObject($object, $parentObject, $this->getParentFieldDescription());
0 ignored issues
show
Bug introduced by
It seems like $this->getParentFieldDescription() can be null; however, setObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
3057
            }
3058
        }
3059
    }
3060
3061
    /**
3062
     * {@inheritdoc}
3063
     */
3064
    private function buildDatagrid(): void
3065
    {
3066
        if ($this->loaded['datagrid']) {
3067
            return;
3068
        }
3069
3070
        $this->loaded['datagrid'] = true;
3071
3072
        $filterParameters = $this->getFilterParameters();
3073
3074
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
3075
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
3076
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
3077
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
3078
            } else {
3079
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
3080
                    $this->getClass(),
3081
                    $filterParameters['_sort_by'],
3082
                    []
3083
                );
3084
3085
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
3086
            }
3087
        }
3088
3089
        // initialize the datagrid
3090
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
3091
3092
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
3093
3094
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
3095
3096
        // build the datagrid filter
3097
        $this->configureDatagridFilters($mapper);
3098
3099
        // ok, try to limit to add parent filter
3100
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
3101
            $mapper->add($this->getParentAssociationMapping(), null, [
3102
                'show_filter' => false,
3103
                'label' => false,
3104
                'field_type' => ModelHiddenType::class,
3105
                'field_options' => [
3106
                    'model_manager' => $this->getModelManager(),
3107
                ],
3108
                'operator_type' => HiddenType::class,
3109
            ], null, null, [
3110
                'admin_code' => $this->getParent()->getCode(),
3111
            ]);
3112
        }
3113
3114
        foreach ($this->getExtensions() as $extension) {
3115
            $extension->configureDatagridFilters($mapper);
3116
        }
3117
    }
3118
3119
    /**
3120
     * Build all the related urls to the current admin.
3121
     */
3122
    private function buildRoutes(): void
3123
    {
3124
        if ($this->loaded['routes']) {
3125
            return;
3126
        }
3127
3128
        $this->loaded['routes'] = true;
3129
3130
        $this->routes = new RouteCollection(
3131
            $this->getBaseCodeRoute(),
3132
            $this->getBaseRouteName(),
3133
            $this->getBaseRoutePattern(),
3134
            $this->getBaseControllerName()
3135
        );
3136
3137
        $this->routeBuilder->build($this, $this->routes);
3138
3139
        $this->configureRoutes($this->routes);
3140
3141
        foreach ($this->getExtensions() as $extension) {
3142
            $extension->configureRoutes($this, $this->routes);
3143
        }
3144
    }
3145
}
3146
3147
class_exists(ErrorElement::class);
3148