Completed
Pull Request — master (#6209)
by Jordi Sala
03:49 queued 01:04
created

AbstractAdmin::attachAdminClass()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 33
rs 8.7697
c 0
b 0
f 0
cc 6
nc 9
nop 1
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\Translation\TranslatorInterface;
58
use Symfony\Component\Validator\Mapping\GenericMetadata;
59
use Symfony\Component\Validator\Validator\ValidatorInterface;
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 \Symfony\Component\Translation\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
    /**
2035
     * Translate a message id.
2036
     *
2037
     * NEXT_MAJOR: remove this method
2038
     *
2039
     * @return string the translated string
2040
     *
2041
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2042
     */
2043
    public function transChoice(string $id, int $count, array $parameters = [], ?string $domain = null, ?string $locale = null): string
2044
    {
2045
        @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...
2046
            'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2047
            __METHOD__
2048
        ), E_USER_DEPRECATED);
2049
2050
        $domain = $domain ?: $this->getTranslationDomain();
2051
2052
        return $this->translator->transChoice($id, $count, $parameters, $domain, $locale);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since 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...
2053
    }
2054
2055
    public function setTranslationDomain(string $translationDomain): void
2056
    {
2057
        $this->translationDomain = $translationDomain;
2058
    }
2059
2060
    public function getTranslationDomain(): string
2061
    {
2062
        return $this->translationDomain;
2063
    }
2064
2065
    /**
2066
     * {@inheritdoc}
2067
     *
2068
     * NEXT_MAJOR: remove this method
2069
     *
2070
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2071
     */
2072
    public function setTranslator(?TranslatorInterface $translator): void
2073
    {
2074
        $args = \func_get_args();
2075
        if (isset($args[1]) && $args[1]) {
2076
            @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...
2077
                'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2078
                __METHOD__
2079
            ), E_USER_DEPRECATED);
2080
        }
2081
2082
        $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...
2083
    }
2084
2085
    /**
2086
     * {@inheritdoc}
2087
     *
2088
     * NEXT_MAJOR: remove this method
2089
     *
2090
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2091
     */
2092
    public function getTranslator(): ?TranslatorInterface
2093
    {
2094
        @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...
2095
            'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2096
            __METHOD__
2097
        ), E_USER_DEPRECATED);
2098
2099
        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...
2100
    }
2101
2102
    public function getTranslationLabel(string $label, string $context = '', string $type = ''): string
2103
    {
2104
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2105
    }
2106
2107
    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...
2108
    {
2109
        $this->request = $request;
2110
2111
        foreach ($this->getChildren() as $children) {
2112
            $children->setRequest($request);
2113
        }
2114
    }
2115
2116
    public function getRequest(): Request
2117
    {
2118
        if (!$this->request) {
2119
            // NEXT_MAJOR: Throw \LogicException instead.
2120
            throw new \RuntimeException('The Request object has not been set');
2121
        }
2122
2123
        return $this->request;
2124
    }
2125
2126
    public function hasRequest(): bool
2127
    {
2128
        return null !== $this->request;
2129
    }
2130
2131
    public function setFormContractor(?FormContractorInterface $formBuilder): void
2132
    {
2133
        $this->formContractor = $formBuilder;
2134
    }
2135
2136
    public function getFormContractor(): ?FormContractorInterface
2137
    {
2138
        return $this->formContractor;
2139
    }
2140
2141
    public function setDatagridBuilder(?DatagridBuilderInterface $datagridBuilder): void
2142
    {
2143
        $this->datagridBuilder = $datagridBuilder;
2144
    }
2145
2146
    public function getDatagridBuilder(): ?DatagridBuilderInterface
2147
    {
2148
        return $this->datagridBuilder;
2149
    }
2150
2151
    public function setListBuilder(?ListBuilderInterface $listBuilder): void
2152
    {
2153
        $this->listBuilder = $listBuilder;
2154
    }
2155
2156
    public function getListBuilder(): ?ListBuilderInterface
2157
    {
2158
        return $this->listBuilder;
2159
    }
2160
2161
    public function setShowBuilder(?ShowBuilderInterface $showBuilder): void
2162
    {
2163
        $this->showBuilder = $showBuilder;
2164
    }
2165
2166
    public function getShowBuilder(): ?ShowBuilderInterface
2167
    {
2168
        return $this->showBuilder;
2169
    }
2170
2171
    public function setConfigurationPool(?Pool $configurationPool): void
2172
    {
2173
        $this->configurationPool = $configurationPool;
2174
    }
2175
2176
    public function getConfigurationPool(): ?Pool
2177
    {
2178
        return $this->configurationPool;
2179
    }
2180
2181
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator): void
2182
    {
2183
        $this->routeGenerator = $routeGenerator;
2184
    }
2185
2186
    public function getRouteGenerator(): ?RouteGeneratorInterface
2187
    {
2188
        return $this->routeGenerator;
2189
    }
2190
2191
    public function getCode(): string
2192
    {
2193
        return $this->code;
2194
    }
2195
2196
    public function getBaseCodeRoute(): string
2197
    {
2198
        if ($this->isChild()) {
2199
            return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2200
        }
2201
2202
        return $this->getCode();
2203
    }
2204
2205
    public function getModelManager(): ?ModelManagerInterface
2206
    {
2207
        return $this->modelManager;
2208
    }
2209
2210
    public function setModelManager(?ModelManagerInterface $modelManager): void
2211
    {
2212
        $this->modelManager = $modelManager;
2213
    }
2214
2215
    public function getManagerType(): ?string
2216
    {
2217
        return $this->managerType;
2218
    }
2219
2220
    public function setManagerType(?string $type): void
2221
    {
2222
        $this->managerType = $type;
2223
    }
2224
2225
    public function getObjectIdentifier()
2226
    {
2227
        return $this->getCode();
2228
    }
2229
2230
    /**
2231
     * Set the roles and permissions per role.
2232
     */
2233
    public function setSecurityInformation(array $information): void
2234
    {
2235
        $this->securityInformation = $information;
2236
    }
2237
2238
    public function getSecurityInformation(): array
2239
    {
2240
        return $this->securityInformation;
2241
    }
2242
2243
    /**
2244
     * Return the list of permissions the user should have in order to display the admin.
2245
     */
2246
    public function getPermissionsShow(string $context): array
2247
    {
2248
        switch ($context) {
2249
            case self::CONTEXT_DASHBOARD:
2250
            case self::CONTEXT_MENU:
2251
            default:
2252
                return ['LIST'];
2253
        }
2254
    }
2255
2256
    public function showIn(string $context): bool
2257
    {
2258
        switch ($context) {
2259
            case self::CONTEXT_DASHBOARD:
2260
            case self::CONTEXT_MENU:
2261
            default:
2262
                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...
2263
        }
2264
    }
2265
2266
    public function createObjectSecurity(object $object): void
2267
    {
2268
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2269
    }
2270
2271
    public function setSecurityHandler(?SecurityHandlerInterface $securityHandler): void
2272
    {
2273
        $this->securityHandler = $securityHandler;
2274
    }
2275
2276
    public function getSecurityHandler(): ?SecurityHandlerInterface
2277
    {
2278
        return $this->securityHandler;
2279
    }
2280
2281
    /**
2282
     * NEXT_MAJOR: Decide the type declaration for the $name argument, since it is
2283
     * passed as argument 1 for `SecurityHandlerInterface::isGranted()`, which
2284
     * accepts string and array.
2285
     */
2286
    public function isGranted($name, ?object $object = null): bool
2287
    {
2288
        $objectRef = $object ? sprintf('/%s#%s', spl_object_hash($object), $this->id($object)) : '';
2289
        $key = md5(json_encode($name).$objectRef);
2290
2291
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
2292
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2293
        }
2294
2295
        return $this->cacheIsGranted[$key];
2296
    }
2297
2298
    /**
2299
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2300
     * passed as argument 1 for `ModelManagerInterface::getUrlSafeIdentifier()`, which
2301
     * accepts null.
2302
     */
2303
    public function getUrlSafeIdentifier($model): ?string
2304
    {
2305
        return $this->getModelManager()->getUrlSafeIdentifier($model);
2306
    }
2307
2308
    /**
2309
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2310
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
2311
     * accepts null.
2312
     */
2313
    public function getNormalizedIdentifier($model): ?string
2314
    {
2315
        return $this->getModelManager()->getNormalizedIdentifier($model);
2316
    }
2317
2318
    /**
2319
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2320
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
2321
     * accepts null.
2322
     */
2323
    public function id($model): ?string
2324
    {
2325
        return $this->getNormalizedIdentifier($model);
2326
    }
2327
2328
    public function setValidator(?ValidatorInterface $validator): void
2329
    {
2330
        $this->validator = $validator;
2331
    }
2332
2333
    public function getValidator(): ?ValidatorInterface
2334
    {
2335
        return $this->validator;
2336
    }
2337
2338
    public function getShow(): ?FieldDescriptionCollection
2339
    {
2340
        $this->buildShow();
2341
2342
        return $this->show;
2343
    }
2344
2345
    public function setFormTheme(array $formTheme): void
2346
    {
2347
        $this->formTheme = $formTheme;
2348
    }
2349
2350
    public function getFormTheme(): array
2351
    {
2352
        return $this->formTheme;
2353
    }
2354
2355
    public function setFilterTheme(array $filterTheme): void
2356
    {
2357
        $this->filterTheme = $filterTheme;
2358
    }
2359
2360
    public function getFilterTheme(): array
2361
    {
2362
        return $this->filterTheme;
2363
    }
2364
2365
    public function addExtension(AdminExtensionInterface $extension): void
2366
    {
2367
        $this->extensions[] = $extension;
2368
    }
2369
2370
    public function getExtensions(): array
2371
    {
2372
        return $this->extensions;
2373
    }
2374
2375
    public function setMenuFactory(?FactoryInterface $menuFactory): void
2376
    {
2377
        $this->menuFactory = $menuFactory;
2378
    }
2379
2380
    public function getMenuFactory(): ?FactoryInterface
2381
    {
2382
        return $this->menuFactory;
2383
    }
2384
2385
    public function setRouteBuilder(?RouteBuilderInterface $routeBuilder): void
2386
    {
2387
        $this->routeBuilder = $routeBuilder;
2388
    }
2389
2390
    public function getRouteBuilder(): ?RouteBuilderInterface
2391
    {
2392
        return $this->routeBuilder;
2393
    }
2394
2395
    /**
2396
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since there
2397
     * are tests ensuring to accept null (`GetShortObjectDescriptionActionTest::testGetShortObjectDescriptionActionEmptyObjectIdAsJson()`).
2398
     */
2399
    public function toString($object): string
2400
    {
2401
        if (!\is_object($object)) {
2402
            return '';
2403
        }
2404
2405
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2406
            return (string) $object;
2407
        }
2408
2409
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2410
    }
2411
2412
    public function setLabelTranslatorStrategy(?LabelTranslatorStrategyInterface $labelTranslatorStrategy): void
2413
    {
2414
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2415
    }
2416
2417
    public function getLabelTranslatorStrategy(): ?LabelTranslatorStrategyInterface
2418
    {
2419
        return $this->labelTranslatorStrategy;
2420
    }
2421
2422
    public function supportsPreviewMode(): bool
2423
    {
2424
        return $this->supportsPreviewMode;
2425
    }
2426
2427
    /**
2428
     * NEXT_MAJOR: Remove this.
2429
     *
2430
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
2431
     *
2432
     * Set custom per page options.
2433
     */
2434
    public function setPerPageOptions(array $options): void
2435
    {
2436
        @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...
2437
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
2438
            __METHOD__
2439
        ), E_USER_DEPRECATED);
2440
2441
        $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...
2442
    }
2443
2444
    /**
2445
     * Returns predefined per page options.
2446
     */
2447
    public function getPerPageOptions(): array
2448
    {
2449
        // NEXT_MAJOR: Remove this line and uncomment the following
2450
        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...
2451
//        $perPageOptions = $this->getModelManager()->getDefaultPerPageOptions($this->class);
2452
//        $perPageOptions[] = $this->getMaxPerPage();
2453
//
2454
//        $perPageOptions = array_unique($perPageOptions);
2455
//        sort($perPageOptions);
2456
//
2457
//        return $perPageOptions;
2458
    }
2459
2460
    /**
2461
     * Set pager type.
2462
     */
2463
    public function setPagerType(string $pagerType): void
2464
    {
2465
        $this->pagerType = $pagerType;
2466
    }
2467
2468
    /**
2469
     * Get pager type.
2470
     */
2471
    public function getPagerType(): string
2472
    {
2473
        return $this->pagerType;
2474
    }
2475
2476
    /**
2477
     * Returns true if the per page value is allowed, false otherwise.
2478
     */
2479
    public function determinedPerPageValue(int $perPage): bool
2480
    {
2481
        return \in_array($perPage, $this->getPerPageOptions(), true);
2482
    }
2483
2484
    public function isAclEnabled(): bool
2485
    {
2486
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2487
    }
2488
2489
    /**
2490
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since it is
2491
     * passed as argument 1 to `toString()` method, which currently accepts null.
2492
     */
2493
    public function getObjectMetadata($object): MetadataInterface
2494
    {
2495
        return new Metadata($this->toString($object));
2496
    }
2497
2498
    public function getListModes(): array
2499
    {
2500
        return $this->listModes;
2501
    }
2502
2503
    public function setListMode(string $mode): void
2504
    {
2505
        if (!$this->hasRequest()) {
2506
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2507
        }
2508
2509
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2510
    }
2511
2512
    public function getListMode(): string
2513
    {
2514
        if (!$this->hasRequest()) {
2515
            return 'list';
2516
        }
2517
2518
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2519
    }
2520
2521
    public function getAccessMapping(): array
2522
    {
2523
        return $this->accessMapping;
2524
    }
2525
2526
    public function checkAccess(string $action, ?object $object = null): void
2527
    {
2528
        $access = $this->getAccess();
2529
2530
        if (!\array_key_exists($action, $access)) {
2531
            throw new \InvalidArgumentException(sprintf(
2532
                'Action "%s" could not be found in access mapping.'
2533
                .' Please make sure your action is defined into your admin class accessMapping property.',
2534
                $action
2535
            ));
2536
        }
2537
2538
        if (!\is_array($access[$action])) {
2539
            $access[$action] = [$access[$action]];
2540
        }
2541
2542
        foreach ($access[$action] as $role) {
2543
            if (false === $this->isGranted($role, $object)) {
2544
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2545
            }
2546
        }
2547
    }
2548
2549
    /**
2550
     * {@inheritdoc}
2551
     */
2552
    public function hasAccess(string $action, ?object $object = null): bool
2553
    {
2554
        $access = $this->getAccess();
2555
2556
        if (!\array_key_exists($action, $access)) {
2557
            return false;
2558
        }
2559
2560
        if (!\is_array($access[$action])) {
2561
            $access[$action] = [$access[$action]];
2562
        }
2563
2564
        foreach ($access[$action] as $role) {
2565
            if (false === $this->isGranted($role, $object)) {
2566
                return false;
2567
            }
2568
        }
2569
2570
        return true;
2571
    }
2572
2573
    final public function getActionButtons(string $action, ?object $object = null): array
2574
    {
2575
        $buttonList = [];
2576
2577
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
2578
            && $this->hasAccess('create')
2579
            && $this->hasRoute('create')
2580
        ) {
2581
            $buttonList['create'] = [
2582
                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2583
            ];
2584
        }
2585
2586
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
2587
            && $this->canAccessObject('edit', $object)
2588
            && $this->hasRoute('edit')
2589
        ) {
2590
            $buttonList['edit'] = [
2591
                'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2592
            ];
2593
        }
2594
2595
        if (\in_array($action, ['show', 'edit', 'acl'], true)
2596
            && $this->canAccessObject('history', $object)
2597
            && $this->hasRoute('history')
2598
        ) {
2599
            $buttonList['history'] = [
2600
                'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2601
            ];
2602
        }
2603
2604
        if (\in_array($action, ['edit', 'history'], true)
2605
            && $this->isAclEnabled()
2606
            && $this->canAccessObject('acl', $object)
2607
            && $this->hasRoute('acl')
2608
        ) {
2609
            $buttonList['acl'] = [
2610
                'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2611
            ];
2612
        }
2613
2614
        if (\in_array($action, ['edit', 'history', 'acl'], true)
2615
            && $this->canAccessObject('show', $object)
2616
            && \count($this->getShow()) > 0
2617
            && $this->hasRoute('show')
2618
        ) {
2619
            $buttonList['show'] = [
2620
                'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2621
            ];
2622
        }
2623
2624
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
2625
            && $this->hasAccess('list')
2626
            && $this->hasRoute('list')
2627
        ) {
2628
            $buttonList['list'] = [
2629
                'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2630
            ];
2631
        }
2632
2633
        $buttonList = $this->configureActionButtons($buttonList, $action, $object);
2634
2635
        foreach ($this->getExtensions() as $extension) {
2636
            $buttonList = $extension->configureActionButtons($this, $buttonList, $action, $object);
2637
        }
2638
2639
        return $buttonList;
2640
    }
2641
2642
    /**
2643
     * {@inheritdoc}
2644
     */
2645
    public function getDashboardActions(): array
2646
    {
2647
        $actions = [];
2648
2649
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2650
            $actions['create'] = [
2651
                'label' => 'link_add',
2652
                'translation_domain' => 'SonataAdminBundle',
2653
                'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2654
                'url' => $this->generateUrl('create'),
2655
                'icon' => 'plus-circle',
2656
            ];
2657
        }
2658
2659
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2660
            $actions['list'] = [
2661
                'label' => 'link_list',
2662
                'translation_domain' => 'SonataAdminBundle',
2663
                'url' => $this->generateUrl('list'),
2664
                'icon' => 'list',
2665
            ];
2666
        }
2667
2668
        return $actions;
2669
    }
2670
2671
    /**
2672
     * {@inheritdoc}
2673
     */
2674
    final public function showMosaicButton($isShown): void
2675
    {
2676
        if ($isShown) {
2677
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
2678
        } else {
2679
            unset($this->listModes['mosaic']);
2680
        }
2681
    }
2682
2683
    final public function getSearchResultLink(object $object): ?string
2684
    {
2685
        foreach ($this->searchResultActions as $action) {
2686
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2687
                return $this->generateObjectUrl($action, $object);
2688
            }
2689
        }
2690
2691
        return null;
2692
    }
2693
2694
    /**
2695
     * Checks if a filter type is set to a default value.
2696
     */
2697
    final public function isDefaultFilter(string $name): bool
2698
    {
2699
        $filter = $this->getFilterParameters();
2700
        $default = $this->getDefaultFilterValues();
2701
2702
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
2703
            return false;
2704
        }
2705
2706
        return $filter[$name] === $default[$name];
2707
    }
2708
2709
    public function canAccessObject(string $action, ?object $object = null): bool
2710
    {
2711
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2712
    }
2713
2714
    public function configureActionButtons(array $buttonList, string $action, ?object $object = null): array
2715
    {
2716
        return $buttonList;
2717
    }
2718
2719
    /**
2720
     * Hook to run after initialization.
2721
     */
2722
    protected function configure(): void
2723
    {
2724
    }
2725
2726
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
2727
    {
2728
        return $query;
2729
    }
2730
2731
    /**
2732
     * urlize the given word.
2733
     *
2734
     * @param string $sep the separator
2735
     */
2736
    final protected function urlize(string $word, string $sep = '_'): string
2737
    {
2738
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
2739
    }
2740
2741
    final protected function getTemplateRegistry(): MutableTemplateRegistryInterface
2742
    {
2743
        return $this->templateRegistry;
2744
    }
2745
2746
    /**
2747
     * Returns a list of default sort values.
2748
     *
2749
     * @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...
2750
     */
2751
    final protected function getDefaultSortValues(): array
2752
    {
2753
        $defaultSortValues = [];
2754
2755
        $this->configureDefaultSortValues($defaultSortValues);
2756
2757
        foreach ($this->getExtensions() as $extension) {
2758
            // NEXT_MAJOR: remove method check
2759
            if (method_exists($extension, 'configureDefaultSortValues')) {
2760
                $extension->configureDefaultSortValues($this, $defaultSortValues);
2761
            }
2762
        }
2763
2764
        return $defaultSortValues;
2765
    }
2766
2767
    /**
2768
     * Returns a list of default filters.
2769
     */
2770
    final protected function getDefaultFilterValues(): array
2771
    {
2772
        $defaultFilterValues = [];
2773
2774
        $this->configureDefaultFilterValues($defaultFilterValues);
2775
2776
        foreach ($this->getExtensions() as $extension) {
2777
            // NEXT_MAJOR: remove method check
2778
            if (method_exists($extension, 'configureDefaultFilterValues')) {
2779
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2780
            }
2781
        }
2782
2783
        return $defaultFilterValues;
2784
    }
2785
2786
    protected function configureFormFields(FormMapper $form): void
2787
    {
2788
    }
2789
2790
    protected function configureListFields(ListMapper $list): void
2791
    {
2792
    }
2793
2794
    protected function configureDatagridFilters(DatagridMapper $filter): void
2795
    {
2796
    }
2797
2798
    protected function configureShowFields(ShowMapper $show): void
2799
    {
2800
    }
2801
2802
    protected function configureRoutes(RouteCollection $collection): void
2803
    {
2804
    }
2805
2806
    /**
2807
     * Allows you to customize batch actions.
2808
     *
2809
     * @param array $actions List of actions
2810
     */
2811
    protected function configureBatchActions(array $actions): array
2812
    {
2813
        return $actions;
2814
    }
2815
2816
    /**
2817
     * NEXT_MAJOR: remove this method.
2818
     *
2819
     * @deprecated Use configureTabMenu instead
2820
     */
2821
    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...
2822
    {
2823
    }
2824
2825
    /**
2826
     * Configures the tab menu in your admin.
2827
     */
2828
    protected function configureTabMenu(ItemInterface $menu, string $action, ?AdminInterface $childAdmin = null): void
2829
    {
2830
        // Use configureSideMenu not to mess with previous overrides
2831
        // NEXT_MAJOR: remove this line
2832
        $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...
2833
    }
2834
2835
    /**
2836
     * build the view FieldDescription array.
2837
     */
2838
    protected function buildShow(): void
2839
    {
2840
        if ($this->loaded['show']) {
2841
            return;
2842
        }
2843
2844
        $this->loaded['show'] = true;
2845
2846
        $this->show = $this->getShowBuilder()->getBaseList();
2847
        $mapper = new ShowMapper($this->getShowBuilder(), $this->show, $this);
2848
2849
        $this->configureShowFields($mapper);
2850
2851
        foreach ($this->getExtensions() as $extension) {
2852
            $extension->configureShowFields($mapper);
2853
        }
2854
    }
2855
2856
    /**
2857
     * build the list FieldDescription array.
2858
     */
2859
    protected function buildList(): void
2860
    {
2861
        if ($this->loaded['list']) {
2862
            return;
2863
        }
2864
2865
        $this->loaded['list'] = true;
2866
2867
        $this->list = $this->getListBuilder()->getBaseList();
2868
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
2869
2870
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
2871
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2872
                $this->getClass(),
2873
                'batch',
2874
                [
2875
                    'label' => 'batch',
2876
                    'code' => '_batch',
2877
                    'sortable' => false,
2878
                    'virtual_field' => true,
2879
                ]
2880
            );
2881
2882
            $fieldDescription->setAdmin($this);
2883
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
2884
2885
            $mapper->add($fieldDescription, ListMapper::TYPE_BATCH);
2886
        }
2887
2888
        $this->configureListFields($mapper);
2889
2890
        foreach ($this->getExtensions() as $extension) {
2891
            $extension->configureListFields($mapper);
2892
        }
2893
2894
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
2895
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2896
                $this->getClass(),
2897
                'select',
2898
                [
2899
                    'label' => false,
2900
                    'code' => '_select',
2901
                    'sortable' => false,
2902
                    'virtual_field' => false,
2903
                ]
2904
            );
2905
2906
            $fieldDescription->setAdmin($this);
2907
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
2908
2909
            $mapper->add($fieldDescription, ListMapper::TYPE_SELECT);
2910
        }
2911
    }
2912
2913
    /**
2914
     * Build the form FieldDescription collection.
2915
     */
2916
    protected function buildForm(): void
2917
    {
2918
        if ($this->loaded['form']) {
2919
            return;
2920
        }
2921
2922
        $this->loaded['form'] = true;
2923
2924
        $formBuilder = $this->getFormBuilder();
2925
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
2926
            $this->preValidate($event->getData());
2927
        }, 100);
2928
2929
        $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...
2930
    }
2931
2932
    /**
2933
     * Gets the subclass corresponding to the given name.
2934
     *
2935
     * @param string $name The name of the sub class
2936
     *
2937
     * @return string the subclass
2938
     */
2939
    protected function getSubClass(string $name): string
2940
    {
2941
        if ($this->hasSubClass($name)) {
2942
            return $this->subClasses[$name];
2943
        }
2944
2945
        // NEXT_MAJOR: Throw \LogicException instead.
2946
        throw new \RuntimeException(sprintf('Unable to find the subclass `%s` for admin `%s`', $name, static::class));
2947
    }
2948
2949
    /**
2950
     * Attach the inline validator to the model metadata, this must be done once per admin.
2951
     */
2952
    protected function attachInlineValidator(): void
2953
    {
2954
        $admin = $this;
2955
2956
        // add the custom inline validation option
2957
        $metadata = $this->validator->getMetadataFor($this->getClass());
2958
        if (!$metadata instanceof GenericMetadata) {
2959
            throw new \UnexpectedValueException(
2960
                sprintf(
2961
                    'Cannot add inline validator for %s because its metadata is an instance of %s instead of %s',
2962
                    $this->getClass(),
2963
                    \get_class($metadata),
2964
                    GenericMetadata::class
2965
                )
2966
            );
2967
        }
2968
2969
        $metadata->addConstraint(new InlineConstraint([
2970
            'service' => $this,
2971
            'method' => static function (ErrorElement $errorElement, $object) use ($admin): void {
2972
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
2973
2974
                // This avoid the main validation to be cascaded to children
2975
                // The problem occurs when a model Page has a collection of Page as property
2976
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
2977
                    return;
2978
                }
2979
2980
                $admin->validate($errorElement, $object);
2981
2982
                foreach ($admin->getExtensions() as $extension) {
2983
                    $extension->validate($admin, $errorElement, $object);
2984
                }
2985
            },
2986
            'serializingWarning' => true,
2987
        ]));
2988
    }
2989
2990
    /**
2991
     * NEXT_MAJOR: Remove this function.
2992
     *
2993
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
2994
     *
2995
     * Predefine per page options.
2996
     */
2997
    protected function predefinePerPageOptions(): void
2998
    {
2999
        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...
3000
        $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...
3001
        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...
3002
    }
3003
3004
    /**
3005
     * Return list routes with permissions name.
3006
     *
3007
     * @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...
3008
     */
3009
    protected function getAccess(): array
3010
    {
3011
        $access = array_merge([
3012
            'acl' => 'MASTER',
3013
            'export' => 'EXPORT',
3014
            'historyCompareRevisions' => 'EDIT',
3015
            'historyViewRevision' => 'EDIT',
3016
            'history' => 'EDIT',
3017
            'edit' => 'EDIT',
3018
            'show' => 'VIEW',
3019
            'create' => 'CREATE',
3020
            'delete' => 'DELETE',
3021
            'batchDelete' => 'DELETE',
3022
            'list' => 'LIST',
3023
        ], $this->getAccessMapping());
3024
3025
        foreach ($this->extensions as $extension) {
3026
            $access = array_merge($access, $extension->getAccessMapping($this));
3027
        }
3028
3029
        return $access;
3030
    }
3031
3032
    /**
3033
     * Configures a list of default filters.
3034
     */
3035
    protected function configureDefaultFilterValues(array &$filterValues): void
3036
    {
3037
    }
3038
3039
    /**
3040
     * Configures a list of default sort values.
3041
     *
3042
     * Example:
3043
     *   $sortValues['_sort_by'] = 'foo'
3044
     *   $sortValues['_sort_order'] = 'DESC'
3045
     */
3046
    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...
3047
    {
3048
    }
3049
3050
    /**
3051
     * Set the parent object, if any, to the provided object.
3052
     */
3053
    final protected function appendParentObject(object $object): void
3054
    {
3055
        if ($this->isChild() && $this->getParentAssociationMapping()) {
3056
            $parentAdmin = $this->getParent();
3057
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
3058
3059
            if (null !== $parentObject) {
3060
                $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3061
                $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3062
3063
                $value = $propertyAccessor->getValue($object, $propertyPath);
3064
3065
                if (\is_array($value) || $value instanceof \ArrayAccess) {
3066
                    $value[] = $parentObject;
3067
                    $propertyAccessor->setValue($object, $propertyPath, $value);
3068
                } else {
3069
                    $propertyAccessor->setValue($object, $propertyPath, $parentObject);
3070
                }
3071
            }
3072
        } elseif ($this->hasParentFieldDescription()) {
3073
            $parentAdmin = $this->getParentFieldDescription()->getAdmin();
3074
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
3075
3076
            if (null !== $parentObject) {
3077
                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...
3078
            }
3079
        }
3080
    }
3081
3082
    /**
3083
     * {@inheritdoc}
3084
     */
3085
    private function buildDatagrid(): void
3086
    {
3087
        if ($this->loaded['datagrid']) {
3088
            return;
3089
        }
3090
3091
        $this->loaded['datagrid'] = true;
3092
3093
        $filterParameters = $this->getFilterParameters();
3094
3095
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
3096
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
3097
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
3098
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
3099
            } else {
3100
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
3101
                    $this->getClass(),
3102
                    $filterParameters['_sort_by'],
3103
                    []
3104
                );
3105
3106
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
3107
            }
3108
        }
3109
3110
        // initialize the datagrid
3111
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
3112
3113
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
3114
3115
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
3116
3117
        // build the datagrid filter
3118
        $this->configureDatagridFilters($mapper);
3119
3120
        // ok, try to limit to add parent filter
3121
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
3122
            $mapper->add($this->getParentAssociationMapping(), null, [
3123
                'show_filter' => false,
3124
                'label' => false,
3125
                'field_type' => ModelHiddenType::class,
3126
                'field_options' => [
3127
                    'model_manager' => $this->getModelManager(),
3128
                ],
3129
                'operator_type' => HiddenType::class,
3130
            ], null, null, [
3131
                'admin_code' => $this->getParent()->getCode(),
3132
            ]);
3133
        }
3134
3135
        foreach ($this->getExtensions() as $extension) {
3136
            $extension->configureDatagridFilters($mapper);
3137
        }
3138
    }
3139
3140
    /**
3141
     * Build all the related urls to the current admin.
3142
     */
3143
    private function buildRoutes(): void
3144
    {
3145
        if ($this->loaded['routes']) {
3146
            return;
3147
        }
3148
3149
        $this->loaded['routes'] = true;
3150
3151
        $this->routes = new RouteCollection(
3152
            $this->getBaseCodeRoute(),
3153
            $this->getBaseRouteName(),
3154
            $this->getBaseRoutePattern(),
3155
            $this->getBaseControllerName()
3156
        );
3157
3158
        $this->routeBuilder->build($this, $this->routes);
3159
3160
        $this->configureRoutes($this->routes);
3161
3162
        foreach ($this->getExtensions() as $extension) {
3163
            $extension->configureRoutes($this, $this->routes);
3164
        }
3165
    }
3166
}
3167
3168
class_exists(ErrorElement::class);
3169