Completed
Pull Request — master (#6093)
by Mathieu
29:00
created

AbstractAdmin::transChoice()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

Loading history...
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$maxPerPage has been deprecated with message: since sonata-project/admin-bundle 3.67.

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

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

Loading history...
573
    }
574
575
    /**
576
     * {@inheritdoc}
577
     */
578
    public function getExportFormats(): array
579
    {
580
        return [
581
            'json', 'xml', 'csv', 'xls',
582
        ];
583
    }
584
585
    /**
586
     * {@inheritdoc}
587
     */
588
    public function getExportFields(): array
589
    {
590
        $fields = $this->getModelManager()->getExportFields($this->getClass());
591
592
        foreach ($this->getExtensions() as $extension) {
593
            if (method_exists($extension, 'configureExportFields')) {
594
                $fields = $extension->configureExportFields($this, $fields);
595
            }
596
        }
597
598
        return $fields;
599
    }
600
601
    public function getDataSourceIterator(): SourceIteratorInterface
602
    {
603
        $datagrid = $this->getDatagrid();
604
        $datagrid->buildPager();
605
606
        $fields = [];
607
608
        foreach ($this->getExportFields() as $key => $field) {
609
            $label = $this->getTranslationLabel($field, 'export', 'label');
610
611
            // NEXT_MAJOR: We have to find another way to have a translated label or stop deprecating the translator.
612
            $transLabel = $this->trans($label);
613
614
            // NEXT_MAJOR: Remove the following code in favor of the commented one.
615
            // If a key is provided we use it otherwise we use the generated label.
616
            // $fieldKey = \is_string($key) ? $key : $transLabel;
617
            // $fields[$fieldKey] = $field;
618
            if ($transLabel === $label) {
619
                $fields[$key] = $field;
620
            } else {
621
                $fields[$transLabel] = $field;
622
            }
623
        }
624
625
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
626
    }
627
628
    public function validate(ErrorElement $errorElement, $object): void
629
    {
630
    }
631
632
    /**
633
     * define custom variable.
634
     */
635
    public function initialize(): void
636
    {
637
        if (!$this->classnameLabel) {
638
            /* NEXT_MAJOR: remove cast to string, null is not supposed to be
639
            supported but was documented as such */
640
            $this->classnameLabel = substr(
641
                (string) $this->getClass(),
642
                strrpos((string) $this->getClass(), '\\') + 1
643
            );
644
        }
645
646
        $this->configure();
647
    }
648
649
    public function update(object $object): object
650
    {
651
        $this->preUpdate($object);
652
        foreach ($this->extensions as $extension) {
653
            $extension->preUpdate($this, $object);
654
        }
655
656
        $result = $this->getModelManager()->update($object);
657
        // BC compatibility
658
        if (null !== $result) {
659
            $object = $result;
660
        }
661
662
        $this->postUpdate($object);
663
        foreach ($this->extensions as $extension) {
664
            $extension->postUpdate($this, $object);
665
        }
666
667
        return $object;
668
    }
669
670
    public function create(object $object): object
671
    {
672
        $this->prePersist($object);
673
        foreach ($this->extensions as $extension) {
674
            $extension->prePersist($this, $object);
675
        }
676
677
        $result = $this->getModelManager()->create($object);
678
        // BC compatibility
679
        if (null !== $result) {
680
            $object = $result;
681
        }
682
683
        $this->postPersist($object);
684
        foreach ($this->extensions as $extension) {
685
            $extension->postPersist($this, $object);
686
        }
687
688
        $this->createObjectSecurity($object);
689
690
        return $object;
691
    }
692
693
    public function delete(object $object): void
694
    {
695
        $this->preRemove($object);
696
        foreach ($this->extensions as $extension) {
697
            $extension->preRemove($this, $object);
698
        }
699
700
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
701
        $this->getModelManager()->delete($object);
702
703
        $this->postRemove($object);
704
        foreach ($this->extensions as $extension) {
705
            $extension->postRemove($this, $object);
706
        }
707
    }
708
709
    public function preValidate(object $object): void
710
    {
711
    }
712
713
    public function preUpdate(object $object): void
714
    {
715
    }
716
717
    public function postUpdate(object $object): void
718
    {
719
    }
720
721
    public function prePersist(object $object): void
722
    {
723
    }
724
725
    public function postPersist(object $object): void
726
    {
727
    }
728
729
    public function preRemove(object $object): void
730
    {
731
    }
732
733
    public function postRemove(object $object): void
734
    {
735
    }
736
737
    public function preBatchAction(string $actionName, ProxyQueryInterface $query, array &$idx, bool $allElements = false): void
738
    {
739
    }
740
741
    public function getFilterParameters(): array
742
    {
743
        $parameters = [];
744
745
        // build the values array
746
        if ($this->hasRequest()) {
747
            $filters = $this->request->query->get('filter', []);
748
            if (isset($filters['_page'])) {
749
                $filters['_page'] = (int) $filters['_page'];
750
            }
751
            if (isset($filters['_per_page'])) {
752
                $filters['_per_page'] = (int) $filters['_per_page'];
753
            }
754
755
            // if filter persistence is configured
756
            // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
757
            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...
758
                // if reset filters is asked, remove from storage
759
                if ('reset' === $this->request->query->get('filters')) {
760
                    $this->filterPersister->reset($this->getCode());
761
                }
762
763
                // if no filters, fetch from storage
764
                // otherwise save to storage
765
                if (empty($filters)) {
766
                    $filters = $this->filterPersister->get($this->getCode());
767
                } else {
768
                    $this->filterPersister->set($this->getCode(), $filters);
769
                }
770
            }
771
772
            $parameters = array_merge(
773
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
774
                $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...
775
                $this->getDefaultSortValues(),
776
                $this->getDefaultFilterValues(),
777
                $filters
778
            );
779
780
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
781
                $parameters['_per_page'] = $this->getMaxPerPage();
782
            }
783
784
            // always force the parent value
785
            if ($this->isChild() && $this->getParentAssociationMapping()) {
786
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
787
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
788
            }
789
        }
790
791
        return $parameters;
792
    }
793
794
    /**
795
     * Returns the name of the parent related field, so the field can be use to set the default
796
     * value (ie the parent object) or to filter the object.
797
     *
798
     * @throws \InvalidArgumentException
799
     */
800
    public function getParentAssociationMapping(): ?string
801
    {
802
        // NEXT_MAJOR: remove array check
803
        if (\is_array($this->parentAssociationMapping) && $this->isChild()) {
804
            $parent = $this->getParent()->getCode();
805
806
            if (\array_key_exists($parent, $this->parentAssociationMapping)) {
807
                return $this->parentAssociationMapping[$parent];
808
            }
809
810
            throw new \InvalidArgumentException(sprintf(
811
                'There\'s no association between %s and %s.',
812
                $this->getCode(),
813
                $this->getParent()->getCode()
814
            ));
815
        }
816
817
        // NEXT_MAJOR: remove this line
818
        return $this->parentAssociationMapping;
819
    }
820
821
    final public function addParentAssociationMapping(string $code, string $value): void
822
    {
823
        $this->parentAssociationMapping[$code] = $value;
824
    }
825
826
    /**
827
     * Returns the baseRoutePattern used to generate the routing information.
828
     *
829
     * @throws \RuntimeException
830
     *
831
     * @return string the baseRoutePattern used to generate the routing information
832
     */
833
    public function getBaseRoutePattern(): string
834
    {
835
        if (null !== $this->cachedBaseRoutePattern) {
836
            return $this->cachedBaseRoutePattern;
837
        }
838
839
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
840
            $baseRoutePattern = $this->baseRoutePattern;
841
            if (!$this->baseRoutePattern) {
842
                preg_match(self::CLASS_REGEX, $this->class, $matches);
843
844
                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...
845
                    throw new \RuntimeException(sprintf(
846
                        'Please define a default `baseRoutePattern` value for the admin class `%s`',
847
                        static::class
848
                    ));
849
                }
850
                $baseRoutePattern = $this->urlize($matches[5], '-');
851
            }
852
853
            $this->cachedBaseRoutePattern = sprintf(
854
                '%s/%s/%s',
855
                $this->getParent()->getBaseRoutePattern(),
856
                $this->getParent()->getRouterIdParameter(),
857
                $baseRoutePattern
858
            );
859
        } elseif ($this->baseRoutePattern) {
860
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
861
        } else {
862
            preg_match(self::CLASS_REGEX, $this->class, $matches);
863
864
            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...
865
                throw new \RuntimeException(sprintf(
866
                    'Please define a default `baseRoutePattern` value for the admin class `%s`',
867
                    static::class
868
                ));
869
            }
870
871
            $this->cachedBaseRoutePattern = sprintf(
872
                '/%s%s/%s',
873
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
874
                $this->urlize($matches[3], '-'),
875
                $this->urlize($matches[5], '-')
876
            );
877
        }
878
879
        return $this->cachedBaseRoutePattern;
880
    }
881
882
    /**
883
     * Returns the baseRouteName used to generate the routing information.
884
     *
885
     * @throws \RuntimeException
886
     *
887
     * @return string the baseRouteName used to generate the routing information
888
     */
889
    public function getBaseRouteName(): string
890
    {
891
        if (null !== $this->cachedBaseRouteName) {
892
            return $this->cachedBaseRouteName;
893
        }
894
895
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
896
            $baseRouteName = $this->baseRouteName;
897
            if (!$this->baseRouteName) {
898
                preg_match(self::CLASS_REGEX, $this->class, $matches);
899
900
                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...
901
                    throw new \RuntimeException(sprintf(
902
                        'Cannot automatically determine base route name,'
903
                        .' please define a default `baseRouteName` value for the admin class `%s`',
904
                        static::class
905
                    ));
906
                }
907
                $baseRouteName = $this->urlize($matches[5]);
908
            }
909
910
            $this->cachedBaseRouteName = sprintf(
911
                '%s_%s',
912
                $this->getParent()->getBaseRouteName(),
913
                $baseRouteName
914
            );
915
        } elseif ($this->baseRouteName) {
916
            $this->cachedBaseRouteName = $this->baseRouteName;
917
        } else {
918
            preg_match(self::CLASS_REGEX, $this->class, $matches);
919
920
            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...
921
                throw new \RuntimeException(sprintf(
922
                    'Cannot automatically determine base route name,'
923
                    .' please define a default `baseRouteName` value for the admin class `%s`',
924
                    static::class
925
                ));
926
            }
927
928
            $this->cachedBaseRouteName = sprintf(
929
                'admin_%s%s_%s',
930
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
931
                $this->urlize($matches[3]),
932
                $this->urlize($matches[5])
933
            );
934
        }
935
936
        return $this->cachedBaseRouteName;
937
    }
938
939
    public function getClass(): string
940
    {
941
        if ($this->hasActiveSubClass()) {
942
            if ($this->hasParentFieldDescription()) {
943
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
944
            }
945
946
            $subClass = $this->getRequest()->query->get('subclass');
947
948
            if (!$this->hasSubClass($subClass)) {
949
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
950
            }
951
952
            return $this->getSubClass($subClass);
953
        }
954
955
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
956
        if ($this->subject && \is_object($this->subject)) {
957
            return ClassUtils::getClass($this->subject);
958
        }
959
960
        return $this->class;
961
    }
962
963
    public function getSubClasses(): array
964
    {
965
        return $this->subClasses;
966
    }
967
968
    /**
969
     * NEXT_MAJOR: remove this method.
970
     */
971
    public function addSubClass($subClass): void
972
    {
973
        @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...
974
            'Method "%s" is deprecated since sonata-project/admin-bundle 3.30 and will be removed in 4.0.',
975
            __METHOD__
976
        ), E_USER_DEPRECATED);
977
978
        if (!\in_array($subClass, $this->subClasses, true)) {
979
            $this->subClasses[] = $subClass;
980
        }
981
    }
982
983
    public function setSubClasses(array $subClasses): void
984
    {
985
        $this->subClasses = $subClasses;
986
    }
987
988
    public function hasSubClass(string $name): bool
989
    {
990
        return isset($this->subClasses[$name]);
991
    }
992
993
    public function hasActiveSubClass(): bool
994
    {
995
        if (\count($this->subClasses) > 0 && $this->request) {
996
            return null !== $this->getRequest()->query->get('subclass');
997
        }
998
999
        return false;
1000
    }
1001
1002
    public function getActiveSubClass(): string
1003
    {
1004
        if (!$this->hasActiveSubClass()) {
1005
            throw new \LogicException(sprintf(
1006
                'Admin "%s" has no active subclass.',
1007
                static::class
1008
            ));
1009
        }
1010
1011
        return $this->getSubClass($this->getActiveSubclassCode());
1012
    }
1013
1014
    public function getActiveSubclassCode(): string
1015
    {
1016
        if (!$this->hasActiveSubClass()) {
1017
            throw new \LogicException(sprintf(
1018
                'Admin "%s" has no active subclass.',
1019
                static::class
1020
            ));
1021
        }
1022
1023
        $subClass = $this->getRequest()->query->get('subclass');
1024
1025
        if (!$this->hasSubClass($subClass)) {
1026
            throw new \LogicException(sprintf(
1027
                'Admin "%s" has no active subclass.',
1028
                static::class
1029
            ));
1030
        }
1031
1032
        return $subClass;
1033
    }
1034
1035
    public function getBatchActions(): array
1036
    {
1037
        $actions = [];
1038
1039
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1040
            $actions['delete'] = [
1041
                'label' => 'action_delete',
1042
                'translation_domain' => 'SonataAdminBundle',
1043
                'ask_confirmation' => true, // by default always true
1044
            ];
1045
        }
1046
1047
        $actions = $this->configureBatchActions($actions);
1048
1049
        foreach ($this->getExtensions() as $extension) {
1050
            $actions = $extension->configureBatchActions($this, $actions);
1051
        }
1052
1053
        foreach ($actions  as $name => &$action) {
1054
            if (!\array_key_exists('label', $action)) {
1055
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1056
            }
1057
1058
            if (!\array_key_exists('translation_domain', $action)) {
1059
                $action['translation_domain'] = $this->getTranslationDomain();
1060
            }
1061
        }
1062
1063
        return $actions;
1064
    }
1065
1066
    /**
1067
     * NEXT_MAJOR: Create a `RouteCollectionInterface` and use as return type.
1068
     */
1069
    public function getRoutes(): RouteCollection
1070
    {
1071
        $this->buildRoutes();
1072
1073
        return $this->routes;
1074
    }
1075
1076
    public function getRouterIdParameter(): string
1077
    {
1078
        return sprintf('{%s}', $this->getIdParameter());
1079
    }
1080
1081
    public function getIdParameter(): string
1082
    {
1083
        $parameter = 'id';
1084
1085
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1086
            $parameter = sprintf('child%s', ucfirst($parameter));
1087
        }
1088
1089
        return $parameter;
1090
    }
1091
1092
    public function hasRoute(string $name): bool
1093
    {
1094
        if (!$this->routeGenerator) {
1095
            throw new \RuntimeException('RouteGenerator cannot be null');
1096
        }
1097
1098
        return $this->routeGenerator->hasAdminRoute($this, $name);
1099
    }
1100
1101
    public function isCurrentRoute(string $name, ?string $adminCode = null): bool
1102
    {
1103
        if (!$this->hasRequest()) {
1104
            return false;
1105
        }
1106
1107
        $request = $this->getRequest();
1108
        $route = $request->get('_route');
1109
1110
        if ($adminCode) {
1111
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1112
        } else {
1113
            $admin = $this;
1114
        }
1115
1116
        if (!$admin) {
1117
            return false;
1118
        }
1119
1120
        return sprintf('%s_%s', $admin->getBaseRouteName(), $name) === $route;
1121
    }
1122
1123
    public function generateObjectUrl(string $name, object $object, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1124
    {
1125
        $parameters['id'] = $this->getUrlSafeIdentifier($object);
1126
1127
        return $this->generateUrl($name, $parameters, $referenceType);
1128
    }
1129
1130
    public function generateUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1131
    {
1132
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $referenceType);
1133
    }
1134
1135
    public function generateMenuUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): array
1136
    {
1137
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $referenceType);
1138
    }
1139
1140
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry): void
1141
    {
1142
        $this->templateRegistry = $templateRegistry;
1143
    }
1144
1145
    /**
1146
     * @param array<string, string> $templates
1147
     */
1148
    public function setTemplates(array $templates): void
1149
    {
1150
        $this->getTemplateRegistry()->setTemplates($templates);
1151
    }
1152
1153
    /**
1154
     * {@inheritdoc}
1155
     */
1156
    public function setTemplate(string $name, string $template): void
1157
    {
1158
        $this->getTemplateRegistry()->setTemplate($name, $template);
1159
    }
1160
1161
    public function getNewInstance(): object
1162
    {
1163
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1164
1165
        $this->appendParentObject($object);
1166
1167
        foreach ($this->getExtensions() as $extension) {
1168
            $extension->alterNewInstance($this, $object);
1169
        }
1170
1171
        return $object;
1172
    }
1173
1174
    public function getFormBuilder(): FormBuilderInterface
1175
    {
1176
        $this->formOptions['data_class'] = $this->getClass();
1177
1178
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1179
            $this->getUniqid(),
1180
            $this->formOptions
1181
        );
1182
1183
        $this->defineFormBuilder($formBuilder);
1184
1185
        return $formBuilder;
1186
    }
1187
1188
    /**
1189
     * This method is being called by the main admin class and the child class,
1190
     * the getFormBuilder is only call by the main admin class.
1191
     */
1192
    public function defineFormBuilder(FormBuilderInterface $formBuilder): void
1193
    {
1194
        if (!$this->hasSubject()) {
1195
            @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...
1196
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.65'
1197
                .' and will throw an exception in 4.0. Use %s::setSubject() to set the subject.',
1198
                __METHOD__,
1199
                __CLASS__
1200
            ), E_USER_DEPRECATED);
1201
            // NEXT_MAJOR : remove the previous `trigger_error()` call and uncomment the following exception
1202
            // throw new \LogicException(sprintf(
1203
            //    'Admin "%s" has no subject.',
1204
            //    static::class
1205
            // ));
1206
        }
1207
1208
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1209
1210
        $this->configureFormFields($mapper);
1211
1212
        foreach ($this->getExtensions() as $extension) {
1213
            $extension->configureFormFields($mapper);
1214
        }
1215
1216
        $this->attachInlineValidator();
1217
    }
1218
1219
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription): void
1220
    {
1221
        $pool = $this->getConfigurationPool();
1222
1223
        $adminCode = $fieldDescription->getOption('admin_code');
1224
1225
        if (null !== $adminCode) {
1226
            if (!$pool->hasAdminByAdminCode($adminCode)) {
1227
                return;
1228
            }
1229
1230
            $admin = $pool->getAdminByAdminCode($adminCode);
1231
        } else {
1232
            // NEXT_MAJOR: Remove the check and use `getTargetModel`.
1233
            if (method_exists($fieldDescription, 'getTargetModel')) {
1234
                $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...
1235
            } else {
1236
                $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...
1237
            }
1238
1239
            if (!$pool->hasAdminByClass($targetModel)) {
1240
                return;
1241
            }
1242
1243
            $admin = $pool->getAdminByClass($targetModel);
1244
        }
1245
1246
        if ($this->hasRequest()) {
1247
            $admin->setRequest($this->getRequest());
1248
        }
1249
1250
        $fieldDescription->setAssociationAdmin($admin);
0 ignored issues
show
Bug introduced by
It seems like $admin defined by $pool->getAdminByClass($targetModel) on line 1243 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...
1251
    }
1252
1253
    public function getObject($id): ?object
1254
    {
1255
        $object = $this->getModelManager()->find($this->getClass(), $id);
1256
        foreach ($this->getExtensions() as $extension) {
1257
            $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...
1258
        }
1259
1260
        return $object;
1261
    }
1262
1263
    public function getForm(): ?FormInterface
1264
    {
1265
        $this->buildForm();
1266
1267
        return $this->form;
1268
    }
1269
1270
    public function getList(): ?FieldDescriptionCollection
1271
    {
1272
        $this->buildList();
1273
1274
        return $this->list;
1275
    }
1276
1277
    /**
1278
     * @final since sonata-project/admin-bundle 3.63.0
1279
     */
1280
    public function createQuery($context = 'list'): ProxyQueryInterface
1281
    {
1282
        if (\func_num_args() > 0) {
1283
            @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...
1284
                'The $context argument of %s is deprecated since 3.3, to be removed in 4.0.',
1285
                __METHOD__
1286
            ), E_USER_DEPRECATED);
1287
        }
1288
1289
        $query = $this->getModelManager()->createQuery($this->getClass());
1290
1291
        $query = $this->configureQuery($query);
1292
        foreach ($this->extensions as $extension) {
1293
            $extension->configureQuery($this, $query, $context);
1294
        }
1295
1296
        return $query;
1297
    }
1298
1299
    public function getDatagrid(): DatagridInterface
1300
    {
1301
        $this->buildDatagrid();
1302
1303
        return $this->datagrid;
1304
    }
1305
1306
    public function buildTabMenu(string $action, ?AdminInterface $childAdmin = null): ItemInterface
1307
    {
1308
        if ($this->loaded['tab_menu']) {
1309
            return $this->menu;
1310
        }
1311
1312
        $this->loaded['tab_menu'] = true;
1313
1314
        $menu = $this->menuFactory->createItem('root');
1315
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1316
        $menu->setExtra('translation_domain', $this->translationDomain);
1317
1318
        // Prevents BC break with KnpMenuBundle v1.x
1319
        if (method_exists($menu, 'setCurrentUri')) {
1320
            $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...
1321
        }
1322
1323
        $this->configureTabMenu($menu, $action, $childAdmin);
1324
1325
        foreach ($this->getExtensions() as $extension) {
1326
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1327
        }
1328
1329
        $this->menu = $menu;
1330
1331
        return $this->menu;
1332
    }
1333
1334
    public function getSideMenu(string $action, ?AdminInterface $childAdmin = null): ItemInterface
1335
    {
1336
        if ($this->isChild()) {
1337
            return $this->getParent()->getSideMenu($action, $this);
1338
        }
1339
1340
        $this->buildTabMenu($action, $childAdmin);
1341
1342
        return $this->menu;
1343
    }
1344
1345
    public function getRootCode(): string
1346
    {
1347
        return $this->getRoot()->getCode();
1348
    }
1349
1350
    public function getRoot(): AdminInterface
1351
    {
1352
        if (!$this->hasParentFieldDescription()) {
1353
            return $this;
1354
        }
1355
1356
        return $this->getParentFieldDescription()->getAdmin()->getRoot();
1357
    }
1358
1359
    public function setBaseControllerName(string $baseControllerName): void
1360
    {
1361
        $this->baseControllerName = $baseControllerName;
1362
    }
1363
1364
    public function getBaseControllerName(): string
1365
    {
1366
        return $this->baseControllerName;
1367
    }
1368
1369
    public function setLabel(?string $label): void
1370
    {
1371
        $this->label = $label;
1372
    }
1373
1374
    public function getLabel(): ?string
1375
    {
1376
        return $this->label;
1377
    }
1378
1379
    /**
1380
     * @param bool $persist
1381
     *
1382
     * NEXT_MAJOR: remove this method
1383
     *
1384
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
1385
     */
1386
    public function setPersistFilters(bool $persist): void
1387
    {
1388
        @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...
1389
            'The %s method is deprecated since version 3.34 and will be removed in 4.0.',
1390
            __METHOD__
1391
        ), E_USER_DEPRECATED);
1392
1393
        $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...
1394
    }
1395
1396
    public function setFilterPersister(?FilterPersisterInterface $filterPersister = null): void
1397
    {
1398
        $this->filterPersister = $filterPersister;
1399
        // NEXT_MAJOR remove the deprecated property will be removed. Needed for persisted filter condition.
1400
        $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...
1401
    }
1402
1403
    /**
1404
     * NEXT_MAJOR: Remove this method.
1405
     *
1406
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
1407
     */
1408
    public function setMaxPerPage(int $maxPerPage): void
1409
    {
1410
        @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...
1411
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
1412
            __METHOD__
1413
        ), E_USER_DEPRECATED);
1414
1415
        $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...
1416
    }
1417
1418
    public function getMaxPerPage(): int
1419
    {
1420
        // NEXT_MAJOR: Remove this line and uncomment the following.
1421
        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...
1422
        // $sortValues = $this->getModelManager()->getDefaultSortValues($this->class);
1423
1424
        // return $sortValues['_per_page'] ?? 25;
1425
    }
1426
1427
    public function setMaxPageLinks(int $maxPageLinks): void
1428
    {
1429
        $this->maxPageLinks = $maxPageLinks;
1430
    }
1431
1432
    public function getMaxPageLinks(): int
1433
    {
1434
        return $this->maxPageLinks;
1435
    }
1436
1437
    public function getFormGroups(): array
1438
    {
1439
        return $this->formGroups;
1440
    }
1441
1442
    public function setFormGroups(array $formGroups): void
1443
    {
1444
        $this->formGroups = $formGroups;
1445
    }
1446
1447
    public function removeFieldFromFormGroup(string $key): void
1448
    {
1449
        foreach ($this->formGroups as $name => $formGroup) {
1450
            unset($this->formGroups[$name]['fields'][$key]);
1451
1452
            if (empty($this->formGroups[$name]['fields'])) {
1453
                unset($this->formGroups[$name]);
1454
            }
1455
        }
1456
    }
1457
1458
    public function reorderFormGroup(string $group, array $keys): void
1459
    {
1460
        $formGroups = $this->getFormGroups();
1461
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1462
        $this->setFormGroups($formGroups);
1463
    }
1464
1465
    public function getFormTabs(): array
1466
    {
1467
        return $this->formTabs;
1468
    }
1469
1470
    public function setFormTabs(array $formTabs): void
1471
    {
1472
        $this->formTabs = $formTabs;
1473
    }
1474
1475
    public function getShowTabs(): array
1476
    {
1477
        return $this->showTabs;
1478
    }
1479
1480
    public function setShowTabs(array $showTabs): void
1481
    {
1482
        $this->showTabs = $showTabs;
1483
    }
1484
1485
    public function getShowGroups(): array
1486
    {
1487
        return $this->showGroups;
1488
    }
1489
1490
    public function setShowGroups(array $showGroups): void
1491
    {
1492
        $this->showGroups = $showGroups;
1493
    }
1494
1495
    public function reorderShowGroup(string $group, array $keys): void
1496
    {
1497
        $showGroups = $this->getShowGroups();
1498
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1499
        $this->setShowGroups($showGroups);
1500
    }
1501
1502
    public function setParentFieldDescription(?FieldDescriptionInterface $parentFieldDescription): void
1503
    {
1504
        $this->parentFieldDescription = $parentFieldDescription;
1505
    }
1506
1507
    public function getParentFieldDescription(): ?FieldDescriptionInterface
1508
    {
1509
        if (!$this->hasParentFieldDescription()) {
1510
            @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...
1511
                'Calling %s() when there is no parent field description is deprecated since'
1512
                .' sonata-project/admin-bundle 3.66 and will throw an exception in 4.0.'
1513
                .' Use %s::hasParentFieldDescription() to know if there is a parent field description.',
1514
                __METHOD__,
1515
                __CLASS__
1516
            ), E_USER_DEPRECATED);
1517
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1518
            // throw new \LogicException(sprintf(
1519
            //    'Admin "%s" has no parent field description.',
1520
            //    static::class
1521
            // ));
1522
1523
            return null;
1524
        }
1525
1526
        return $this->parentFieldDescription;
1527
    }
1528
1529
    public function hasParentFieldDescription(): bool
1530
    {
1531
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1532
    }
1533
1534
    public function setSubject(?object $subject): void
1535
    {
1536
        if (\is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1537
            $message = <<<'EOT'
1538
You are trying to set entity an instance of "%s",
1539
which is not the one registered with this admin class ("%s").
1540
This is deprecated since 3.5 and will no longer be supported in 4.0.
1541
EOT;
1542
1543
            // NEXT_MAJOR : throw an exception instead
1544
            @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...
1545
        }
1546
1547
        $this->subject = $subject;
1548
    }
1549
1550
    public function getSubject(): ?object
1551
    {
1552
        if (!$this->hasSubject()) {
1553
            @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...
1554
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.66'
1555
                .' and will throw an exception in 4.0. Use %s::hasSubject() to know if there is a subject.',
1556
                __METHOD__,
1557
                __CLASS__
1558
            ), E_USER_DEPRECATED);
1559
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and update the return type
1560
            // throw new \LogicException(sprintf(
1561
            //    'Admin "%s" has no subject.',
1562
            //    static::class
1563
            // ));
1564
1565
            return null;
1566
        }
1567
1568
        return $this->subject;
1569
    }
1570
1571
    public function hasSubject(): bool
1572
    {
1573
        if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
1574
            $id = $this->request->get($this->getIdParameter());
1575
1576
            if (null !== $id) {
1577
                $this->subject = $this->getObject($id);
1578
            }
1579
        }
1580
1581
        return null !== $this->subject;
1582
    }
1583
1584
    public function getFormFieldDescriptions(): array
1585
    {
1586
        $this->buildForm();
1587
1588
        return $this->formFieldDescriptions;
1589
    }
1590
1591
    public function getFormFieldDescription(string $name): ?FieldDescriptionInterface
1592
    {
1593
        $this->buildForm();
1594
1595
        if (!$this->hasFormFieldDescription($name)) {
1596
            @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...
1597
                'Calling %s() when there is no form field description is deprecated since'
1598
                .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
1599
                .' Use %s::hasFormFieldDescription() to know if there is a form field description.',
1600
                __METHOD__,
1601
                __CLASS__
1602
            ), E_USER_DEPRECATED);
1603
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1604
            // throw new \LogicException(sprintf(
1605
            //    'Admin "%s" has no form field description for the field %s.',
1606
            //    static::class,
1607
            //    $name
1608
            // ));
1609
1610
            return null;
1611
        }
1612
1613
        return $this->formFieldDescriptions[$name];
1614
    }
1615
1616
    /**
1617
     * Returns true if the admin has a FieldDescription with the given $name.
1618
     */
1619
    public function hasFormFieldDescription(string $name): bool
1620
    {
1621
        $this->buildForm();
1622
1623
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1624
    }
1625
1626
    public function addFormFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1627
    {
1628
        $this->formFieldDescriptions[$name] = $fieldDescription;
1629
    }
1630
1631
    /**
1632
     * remove a FieldDescription.
1633
     */
1634
    public function removeFormFieldDescription(string $name): void
1635
    {
1636
        unset($this->formFieldDescriptions[$name]);
1637
    }
1638
1639
    /**
1640
     * build and return the collection of form FieldDescription.
1641
     *
1642
     * @return FieldDescriptionInterface[] collection of form FieldDescription
1643
     */
1644
    public function getShowFieldDescriptions(): array
1645
    {
1646
        $this->buildShow();
1647
1648
        return $this->showFieldDescriptions;
1649
    }
1650
1651
    /**
1652
     * Returns the form FieldDescription with the given $name.
1653
     */
1654
    public function getShowFieldDescription(string $name): ?FieldDescriptionInterface
1655
    {
1656
        $this->buildShow();
1657
1658
        if (!$this->hasShowFieldDescription($name)) {
1659
            @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...
1660
                'Calling %s() when there is no show field description is deprecated since'
1661
                .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
1662
                .' Use %s::hasFormFieldDescription() to know if there is a show field description.',
1663
                __METHOD__,
1664
                __CLASS__
1665
            ), E_USER_DEPRECATED);
1666
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1667
            // throw new \LogicException(sprintf(
1668
            //    'Admin "%s" has no show field description for the field %s.',
1669
            //    static::class,
1670
            //    $name
1671
            // ));
1672
1673
            return null;
1674
        }
1675
1676
        return $this->showFieldDescriptions[$name];
1677
    }
1678
1679
    public function hasShowFieldDescription(string $name): bool
1680
    {
1681
        $this->buildShow();
1682
1683
        return \array_key_exists($name, $this->showFieldDescriptions);
1684
    }
1685
1686
    public function addShowFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1687
    {
1688
        $this->showFieldDescriptions[$name] = $fieldDescription;
1689
    }
1690
1691
    public function removeShowFieldDescription(string $name): void
1692
    {
1693
        unset($this->showFieldDescriptions[$name]);
1694
    }
1695
1696
    public function getListFieldDescriptions(): array
1697
    {
1698
        $this->buildList();
1699
1700
        return $this->listFieldDescriptions;
1701
    }
1702
1703
    public function getListFieldDescription(string $name): ?FieldDescriptionInterface
1704
    {
1705
        $this->buildList();
1706
1707
        if (!$this->hasListFieldDescription($name)) {
1708
            @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...
1709
                'Calling %s() when there is no list field description is deprecated since'
1710
                .' sonata-project/admin-bundle 3.66 and will throw an exception in 4.0.'
1711
                .' Use %s::hasListFieldDescription(\'%s\') to know if there is a list field description.',
1712
                __METHOD__,
1713
                __CLASS__,
1714
                $name
1715
            ), E_USER_DEPRECATED);
1716
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1717
            // throw new \LogicException(sprintf(
1718
            //    'Admin "%s" has no list field description for %s.',
1719
            //    static::class,
1720
            //    $name
1721
            // ));
1722
1723
            return null;
1724
        }
1725
1726
        return $this->listFieldDescriptions[$name];
1727
    }
1728
1729
    public function hasListFieldDescription(string $name): bool
1730
    {
1731
        $this->buildList();
1732
1733
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1734
    }
1735
1736
    public function addListFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1737
    {
1738
        $this->listFieldDescriptions[$name] = $fieldDescription;
1739
    }
1740
1741
    public function removeListFieldDescription(string $name): void
1742
    {
1743
        unset($this->listFieldDescriptions[$name]);
1744
    }
1745
1746
    public function getFilterFieldDescription(string $name): ?FieldDescriptionInterface
1747
    {
1748
        $this->buildDatagrid();
1749
1750
        if (!$this->hasFilterFieldDescription($name)) {
1751
            @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...
1752
                'Calling %s() when there is no filter field description is deprecated since'
1753
                .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
1754
                .' Use %s::hasFilterFieldDescription() to know if there is a filter field description.',
1755
                __METHOD__,
1756
                __CLASS__
1757
            ), E_USER_DEPRECATED);
1758
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1759
            // throw new \LogicException(sprintf(
1760
            //    'Admin "%s" has no filter field description for the field %s.',
1761
            //    static::class,
1762
            //    $name
1763
            // ));
1764
1765
            return null;
1766
        }
1767
1768
        return $this->filterFieldDescriptions[$name];
1769
    }
1770
1771
    public function hasFilterFieldDescription(string $name): bool
1772
    {
1773
        $this->buildDatagrid();
1774
1775
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1776
    }
1777
1778
    public function addFilterFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1779
    {
1780
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1781
    }
1782
1783
    public function removeFilterFieldDescription(string $name): void
1784
    {
1785
        unset($this->filterFieldDescriptions[$name]);
1786
    }
1787
1788
    public function getFilterFieldDescriptions(): array
1789
    {
1790
        $this->buildDatagrid();
1791
1792
        return $this->filterFieldDescriptions;
1793
    }
1794
1795
    public function addChild(AdminInterface $child): void
1796
    {
1797
        $parentAdmin = $this;
1798
        while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
1799
            $parentAdmin = $parentAdmin->getParent();
1800
        }
1801
1802
        if ($parentAdmin->getCode() === $child->getCode()) {
1803
            throw new \RuntimeException(sprintf(
1804
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1805
                $child->getCode(),
1806
                $this->getCode()
1807
            ));
1808
        }
1809
1810
        $this->children[$child->getCode()] = $child;
1811
1812
        $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...
1813
1814
        // NEXT_MAJOR: remove $args and add $field parameter to this function on next Major
1815
1816
        $args = \func_get_args();
1817
1818
        if (isset($args[1])) {
1819
            $child->addParentAssociationMapping($this->getCode(), $args[1]);
1820
        } else {
1821
            @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...
1822
                'Calling "addChild" without second argument is deprecated since sonata-project/admin-bundle 3.35 and will not be allowed in 4.0.',
1823
                E_USER_DEPRECATED
1824
            );
1825
        }
1826
    }
1827
1828
    public function hasChild(string $code): bool
1829
    {
1830
        return isset($this->children[$code]);
1831
    }
1832
1833
    public function getChildren(): array
1834
    {
1835
        return $this->children;
1836
    }
1837
1838
    public function getChild(string $code): ?AdminInterface
1839
    {
1840
        if (!$this->hasChild($code)) {
1841
            @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...
1842
                'Calling %s() when there is no child is deprecated since sonata-project/admin-bundle 3.69'
1843
                .' and will throw an exception in 4.0. Use %s::hasChild() to know if the child exists.',
1844
                __METHOD__,
1845
                __CLASS__
1846
            ), E_USER_DEPRECATED);
1847
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
1848
            // throw new \LogicException(sprintf(
1849
            //    'Admin "%s" has no child for the code %s.',
1850
            //    static::class,
1851
            //    $code
1852
            // ));
1853
1854
            return null;
1855
        }
1856
1857
        return $this->children[$code];
1858
    }
1859
1860
    public function setParent(AdminInterface $parent): void
1861
    {
1862
        $this->parent = $parent;
1863
    }
1864
1865
    public function getParent(): ?AdminInterface
1866
    {
1867
        if (!$this->isChild()) {
1868
            @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...
1869
                'Calling %s() when there is no parent is deprecated since sonata-project/admin-bundle 3.66'
1870
                .' and will throw an exception in 4.0. Use %s::isChild() to know if there is a parent.',
1871
                __METHOD__,
1872
                __CLASS__
1873
            ), E_USER_DEPRECATED);
1874
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
1875
            // throw new \LogicException(sprintf(
1876
            //    'Admin "%s" has no parent.',
1877
            //    static::class
1878
            // ));
1879
1880
            return null;
1881
        }
1882
1883
        return $this->parent;
1884
    }
1885
1886
    final public function getRootAncestor(): AdminInterface
1887
    {
1888
        $parent = $this;
1889
1890
        while ($parent->isChild()) {
1891
            $parent = $parent->getParent();
1892
        }
1893
1894
        return $parent;
1895
    }
1896
1897
    final public function getChildDepth(): int
1898
    {
1899
        $parent = $this;
1900
        $depth = 0;
1901
1902
        while ($parent->isChild()) {
1903
            $parent = $parent->getParent();
1904
            ++$depth;
1905
        }
1906
1907
        return $depth;
1908
    }
1909
1910
    final public function getCurrentLeafChildAdmin(): ?AdminInterface
1911
    {
1912
        $child = $this->getCurrentChildAdmin();
1913
1914
        if (null === $child) {
1915
            return null;
1916
        }
1917
1918
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1919
            $child = $c;
1920
        }
1921
1922
        return $child;
1923
    }
1924
1925
    public function isChild(): bool
1926
    {
1927
        return $this->parent instanceof AdminInterface;
1928
    }
1929
1930
    /**
1931
     * Returns true if the admin has children, false otherwise.
1932
     */
1933
    public function hasChildren(): bool
1934
    {
1935
        return \count($this->children) > 0;
1936
    }
1937
1938
    public function setUniqid(string $uniqid): void
1939
    {
1940
        $this->uniqid = $uniqid;
1941
    }
1942
1943
    public function getUniqid(): string
1944
    {
1945
        if (!$this->uniqid) {
1946
            $this->uniqid = sprintf('s%s', uniqid());
1947
        }
1948
1949
        return $this->uniqid;
1950
    }
1951
1952
    /**
1953
     * {@inheritdoc}
1954
     */
1955
    public function getClassnameLabel(): string
1956
    {
1957
        return $this->classnameLabel;
1958
    }
1959
1960
    public function getPersistentParameters(): array
1961
    {
1962
        $parameters = [];
1963
1964
        foreach ($this->getExtensions() as $extension) {
1965
            $params = $extension->getPersistentParameters($this);
1966
1967
            $parameters = array_merge($parameters, $params);
1968
        }
1969
1970
        return $parameters;
1971
    }
1972
1973
    /**
1974
     * {@inheritdoc}
1975
     */
1976
    public function getPersistentParameter(string $name)
1977
    {
1978
        $parameters = $this->getPersistentParameters();
1979
1980
        return $parameters[$name] ?? null;
1981
    }
1982
1983
    public function setCurrentChild(bool $currentChild): void
1984
    {
1985
        $this->currentChild = $currentChild;
1986
    }
1987
1988
    /**
1989
     * NEXT_MAJOR: Remove this method.
1990
     *
1991
     * @deprecated since sonata-project/admin-bundle 3.65, to be removed in 4.0
1992
     */
1993
    public function getCurrentChild(): bool
1994
    {
1995
        @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...
1996
            'The %s() method is deprecated since version 3.65 and will be removed in 4.0.'
1997
            .' Use %s::isCurrentChild() instead.',
1998
            __METHOD__,
1999
            __CLASS__
2000
        ), E_USER_DEPRECATED);
2001
2002
        return $this->currentChild;
2003
    }
2004
2005
    public function isCurrentChild(): bool
2006
    {
2007
        return $this->currentChild;
2008
    }
2009
2010
    /**
2011
     * Returns the current child admin instance.
2012
     *
2013
     * @return AdminInterface|null the current child admin instance
2014
     */
2015
    public function getCurrentChildAdmin(): ?AdminInterface
2016
    {
2017
        foreach ($this->children as $children) {
2018
            if ($children->isCurrentChild()) {
2019
                return $children;
2020
            }
2021
        }
2022
2023
        return null;
2024
    }
2025
2026
    public function trans($id, array $parameters = [], $domain = null, $locale = null): string
2027
    {
2028
        @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...
2029
            'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2030
            __METHOD__
2031
        ), E_USER_DEPRECATED);
2032
2033
        $domain = $domain ?: $this->getTranslationDomain();
2034
2035
        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...
2036
    }
2037
2038
    public function setTranslationDomain(string $translationDomain): void
2039
    {
2040
        $this->translationDomain = $translationDomain;
2041
    }
2042
2043
    public function getTranslationDomain(): string
2044
    {
2045
        return $this->translationDomain;
2046
    }
2047
2048
    /**
2049
     * {@inheritdoc}
2050
     *
2051
     * NEXT_MAJOR: remove this method
2052
     *
2053
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2054
     */
2055
    public function setTranslator(?TranslatorInterface $translator): void
2056
    {
2057
        $args = \func_get_args();
2058
        if (isset($args[1]) && $args[1]) {
2059
            @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...
2060
                'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2061
                __METHOD__
2062
            ), E_USER_DEPRECATED);
2063
        }
2064
2065
        $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...
2066
    }
2067
2068
    /**
2069
     * {@inheritdoc}
2070
     *
2071
     * NEXT_MAJOR: remove this method
2072
     *
2073
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2074
     */
2075
    public function getTranslator(): ?TranslatorInterface
2076
    {
2077
        @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...
2078
            'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2079
            __METHOD__
2080
        ), E_USER_DEPRECATED);
2081
2082
        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...
2083
    }
2084
2085
    public function getTranslationLabel(string $label, string $context = '', string $type = ''): string
2086
    {
2087
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2088
    }
2089
2090
    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...
2091
    {
2092
        $this->request = $request;
2093
2094
        foreach ($this->getChildren() as $children) {
2095
            $children->setRequest($request);
2096
        }
2097
    }
2098
2099
    public function getRequest(): Request
2100
    {
2101
        if (!$this->request) {
2102
            // NEXT_MAJOR: Throw \LogicException instead.
2103
            throw new \RuntimeException('The Request object has not been set');
2104
        }
2105
2106
        return $this->request;
2107
    }
2108
2109
    public function hasRequest(): bool
2110
    {
2111
        return null !== $this->request;
2112
    }
2113
2114
    public function setFormContractor(?FormContractorInterface $formBuilder): void
2115
    {
2116
        $this->formContractor = $formBuilder;
2117
    }
2118
2119
    public function getFormContractor(): ?FormContractorInterface
2120
    {
2121
        return $this->formContractor;
2122
    }
2123
2124
    public function setDatagridBuilder(?DatagridBuilderInterface $datagridBuilder): void
2125
    {
2126
        $this->datagridBuilder = $datagridBuilder;
2127
    }
2128
2129
    public function getDatagridBuilder(): ?DatagridBuilderInterface
2130
    {
2131
        return $this->datagridBuilder;
2132
    }
2133
2134
    public function setListBuilder(?ListBuilderInterface $listBuilder): void
2135
    {
2136
        $this->listBuilder = $listBuilder;
2137
    }
2138
2139
    public function getListBuilder(): ?ListBuilderInterface
2140
    {
2141
        return $this->listBuilder;
2142
    }
2143
2144
    public function setShowBuilder(?ShowBuilderInterface $showBuilder): void
2145
    {
2146
        $this->showBuilder = $showBuilder;
2147
    }
2148
2149
    public function getShowBuilder(): ?ShowBuilderInterface
2150
    {
2151
        return $this->showBuilder;
2152
    }
2153
2154
    public function setConfigurationPool(?Pool $configurationPool): void
2155
    {
2156
        $this->configurationPool = $configurationPool;
2157
    }
2158
2159
    public function getConfigurationPool(): ?Pool
2160
    {
2161
        return $this->configurationPool;
2162
    }
2163
2164
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator): void
2165
    {
2166
        $this->routeGenerator = $routeGenerator;
2167
    }
2168
2169
    public function getRouteGenerator(): ?RouteGeneratorInterface
2170
    {
2171
        return $this->routeGenerator;
2172
    }
2173
2174
    public function getCode(): string
2175
    {
2176
        return $this->code;
2177
    }
2178
2179
    public function getBaseCodeRoute(): string
2180
    {
2181
        if ($this->isChild()) {
2182
            return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2183
        }
2184
2185
        return $this->getCode();
2186
    }
2187
2188
    public function getModelManager(): ?ModelManagerInterface
2189
    {
2190
        return $this->modelManager;
2191
    }
2192
2193
    public function setModelManager(?ModelManagerInterface $modelManager): void
2194
    {
2195
        $this->modelManager = $modelManager;
2196
    }
2197
2198
    public function getManagerType(): ?string
2199
    {
2200
        return $this->managerType;
2201
    }
2202
2203
    public function setManagerType(?string $type): void
2204
    {
2205
        $this->managerType = $type;
2206
    }
2207
2208
    public function getObjectIdentifier()
2209
    {
2210
        return $this->getCode();
2211
    }
2212
2213
    /**
2214
     * Set the roles and permissions per role.
2215
     */
2216
    public function setSecurityInformation(array $information): void
2217
    {
2218
        $this->securityInformation = $information;
2219
    }
2220
2221
    public function getSecurityInformation(): array
2222
    {
2223
        return $this->securityInformation;
2224
    }
2225
2226
    /**
2227
     * Return the list of permissions the user should have in order to display the admin.
2228
     */
2229
    public function getPermissionsShow(string $context): array
2230
    {
2231
        switch ($context) {
2232
            case self::CONTEXT_DASHBOARD:
2233
            case self::CONTEXT_MENU:
2234
            default:
2235
                return ['LIST'];
2236
        }
2237
    }
2238
2239
    public function showIn(string $context): bool
2240
    {
2241
        switch ($context) {
2242
            case self::CONTEXT_DASHBOARD:
2243
            case self::CONTEXT_MENU:
2244
            default:
2245
                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...
2246
        }
2247
    }
2248
2249
    public function createObjectSecurity(object $object): void
2250
    {
2251
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2252
    }
2253
2254
    public function setSecurityHandler(?SecurityHandlerInterface $securityHandler): void
2255
    {
2256
        $this->securityHandler = $securityHandler;
2257
    }
2258
2259
    public function getSecurityHandler(): ?SecurityHandlerInterface
2260
    {
2261
        return $this->securityHandler;
2262
    }
2263
2264
    /**
2265
     * NEXT_MAJOR: Decide the type declaration for the $name argument, since it is
2266
     * passed as argument 1 for `SecurityHandlerInterface::isGranted()`, which
2267
     * accepts string and array.
2268
     */
2269
    public function isGranted($name, ?object $object = null): bool
2270
    {
2271
        $objectRef = $object ? sprintf('/%s#%s', spl_object_hash($object), $this->id($object)) : '';
2272
        $key = md5(json_encode($name).$objectRef);
2273
2274
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
2275
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2276
        }
2277
2278
        return $this->cacheIsGranted[$key];
2279
    }
2280
2281
    /**
2282
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2283
     * passed as argument 1 for `ModelManagerInterface::getUrlSafeIdentifier()`, which
2284
     * accepts null.
2285
     */
2286
    public function getUrlSafeIdentifier($model): ?string
2287
    {
2288
        return $this->getModelManager()->getUrlSafeIdentifier($model);
2289
    }
2290
2291
    /**
2292
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2293
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
2294
     * accepts null.
2295
     */
2296
    public function getNormalizedIdentifier($model): ?string
2297
    {
2298
        return $this->getModelManager()->getNormalizedIdentifier($model);
2299
    }
2300
2301
    /**
2302
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2303
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
2304
     * accepts null.
2305
     */
2306
    public function id($model): ?string
2307
    {
2308
        return $this->getNormalizedIdentifier($model);
2309
    }
2310
2311
    public function setValidator(?ValidatorInterface $validator): void
2312
    {
2313
        $this->validator = $validator;
2314
    }
2315
2316
    public function getValidator(): ?ValidatorInterface
2317
    {
2318
        return $this->validator;
2319
    }
2320
2321
    public function getShow(): ?FieldDescriptionCollection
2322
    {
2323
        $this->buildShow();
2324
2325
        return $this->show;
2326
    }
2327
2328
    public function setFormTheme(array $formTheme): void
2329
    {
2330
        $this->formTheme = $formTheme;
2331
    }
2332
2333
    public function getFormTheme(): array
2334
    {
2335
        return $this->formTheme;
2336
    }
2337
2338
    public function setFilterTheme(array $filterTheme): void
2339
    {
2340
        $this->filterTheme = $filterTheme;
2341
    }
2342
2343
    public function getFilterTheme(): array
2344
    {
2345
        return $this->filterTheme;
2346
    }
2347
2348
    public function addExtension(AdminExtensionInterface $extension): void
2349
    {
2350
        $this->extensions[] = $extension;
2351
    }
2352
2353
    public function getExtensions(): array
2354
    {
2355
        return $this->extensions;
2356
    }
2357
2358
    public function setMenuFactory(?FactoryInterface $menuFactory): void
2359
    {
2360
        $this->menuFactory = $menuFactory;
2361
    }
2362
2363
    public function getMenuFactory(): ?FactoryInterface
2364
    {
2365
        return $this->menuFactory;
2366
    }
2367
2368
    public function setRouteBuilder(?RouteBuilderInterface $routeBuilder): void
2369
    {
2370
        $this->routeBuilder = $routeBuilder;
2371
    }
2372
2373
    public function getRouteBuilder(): ?RouteBuilderInterface
2374
    {
2375
        return $this->routeBuilder;
2376
    }
2377
2378
    /**
2379
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since there
2380
     * are tests ensuring to accept null (`GetShortObjectDescriptionActionTest::testGetShortObjectDescriptionActionEmptyObjectIdAsJson()`).
2381
     */
2382
    public function toString($object): string
2383
    {
2384
        if (!\is_object($object)) {
2385
            return '';
2386
        }
2387
2388
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2389
            return (string) $object;
2390
        }
2391
2392
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2393
    }
2394
2395
    public function setLabelTranslatorStrategy(?LabelTranslatorStrategyInterface $labelTranslatorStrategy): void
2396
    {
2397
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2398
    }
2399
2400
    public function getLabelTranslatorStrategy(): ?LabelTranslatorStrategyInterface
2401
    {
2402
        return $this->labelTranslatorStrategy;
2403
    }
2404
2405
    public function supportsPreviewMode(): bool
2406
    {
2407
        return $this->supportsPreviewMode;
2408
    }
2409
2410
    /**
2411
     * NEXT_MAJOR: Remove this.
2412
     *
2413
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
2414
     *
2415
     * Set custom per page options.
2416
     */
2417
    public function setPerPageOptions(array $options): void
2418
    {
2419
        @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...
2420
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
2421
            __METHOD__
2422
        ), E_USER_DEPRECATED);
2423
2424
        $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...
2425
    }
2426
2427
    /**
2428
     * Returns predefined per page options.
2429
     */
2430
    public function getPerPageOptions(): array
2431
    {
2432
        // NEXT_MAJOR: Remove this line and uncomment the following
2433
        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...
2434
//        $perPageOptions = $this->getModelManager()->getDefaultPerPageOptions($this->class);
2435
//        $perPageOptions[] = $this->getMaxPerPage();
2436
//
2437
//        $perPageOptions = array_unique($perPageOptions);
2438
//        sort($perPageOptions);
2439
//
2440
//        return $perPageOptions;
2441
    }
2442
2443
    /**
2444
     * Set pager type.
2445
     */
2446
    public function setPagerType(string $pagerType): void
2447
    {
2448
        $this->pagerType = $pagerType;
2449
    }
2450
2451
    /**
2452
     * Get pager type.
2453
     */
2454
    public function getPagerType(): string
2455
    {
2456
        return $this->pagerType;
2457
    }
2458
2459
    /**
2460
     * Returns true if the per page value is allowed, false otherwise.
2461
     */
2462
    public function determinedPerPageValue(int $perPage): bool
2463
    {
2464
        return \in_array($perPage, $this->getPerPageOptions(), true);
2465
    }
2466
2467
    public function isAclEnabled(): bool
2468
    {
2469
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2470
    }
2471
2472
    /**
2473
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since it is
2474
     * passed as argument 1 to `toString()` method, which currently accepts null.
2475
     */
2476
    public function getObjectMetadata($object): MetadataInterface
2477
    {
2478
        return new Metadata($this->toString($object));
2479
    }
2480
2481
    public function getListModes(): array
2482
    {
2483
        return $this->listModes;
2484
    }
2485
2486
    public function setListMode(string $mode): void
2487
    {
2488
        if (!$this->hasRequest()) {
2489
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2490
        }
2491
2492
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2493
    }
2494
2495
    public function getListMode(): string
2496
    {
2497
        if (!$this->hasRequest()) {
2498
            return 'list';
2499
        }
2500
2501
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2502
    }
2503
2504
    public function getAccessMapping(): array
2505
    {
2506
        return $this->accessMapping;
2507
    }
2508
2509
    public function checkAccess(string $action, ?object $object = null): void
2510
    {
2511
        $access = $this->getAccess();
2512
2513
        if (!\array_key_exists($action, $access)) {
2514
            throw new \InvalidArgumentException(sprintf(
2515
                'Action "%s" could not be found in access mapping.'
2516
                .' Please make sure your action is defined into your admin class accessMapping property.',
2517
                $action
2518
            ));
2519
        }
2520
2521
        if (!\is_array($access[$action])) {
2522
            $access[$action] = [$access[$action]];
2523
        }
2524
2525
        foreach ($access[$action] as $role) {
2526
            if (false === $this->isGranted($role, $object)) {
2527
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2528
            }
2529
        }
2530
    }
2531
2532
    /**
2533
     * {@inheritdoc}
2534
     */
2535
    public function hasAccess(string $action, ?object $object = null): bool
2536
    {
2537
        $access = $this->getAccess();
2538
2539
        if (!\array_key_exists($action, $access)) {
2540
            return false;
2541
        }
2542
2543
        if (!\is_array($access[$action])) {
2544
            $access[$action] = [$access[$action]];
2545
        }
2546
2547
        foreach ($access[$action] as $role) {
2548
            if (false === $this->isGranted($role, $object)) {
2549
                return false;
2550
            }
2551
        }
2552
2553
        return true;
2554
    }
2555
2556
    final public function getActionButtons(string $action, ?object $object = null): array
2557
    {
2558
        $buttonList = [];
2559
2560
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
2561
            && $this->hasAccess('create')
2562
            && $this->hasRoute('create')
2563
        ) {
2564
            $buttonList['create'] = [
2565
                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2566
            ];
2567
        }
2568
2569
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
2570
            && $this->canAccessObject('edit', $object)
2571
            && $this->hasRoute('edit')
2572
        ) {
2573
            $buttonList['edit'] = [
2574
                'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2575
            ];
2576
        }
2577
2578
        if (\in_array($action, ['show', 'edit', 'acl'], true)
2579
            && $this->canAccessObject('history', $object)
2580
            && $this->hasRoute('history')
2581
        ) {
2582
            $buttonList['history'] = [
2583
                'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2584
            ];
2585
        }
2586
2587
        if (\in_array($action, ['edit', 'history'], true)
2588
            && $this->isAclEnabled()
2589
            && $this->canAccessObject('acl', $object)
2590
            && $this->hasRoute('acl')
2591
        ) {
2592
            $buttonList['acl'] = [
2593
                'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2594
            ];
2595
        }
2596
2597
        if (\in_array($action, ['edit', 'history', 'acl'], true)
2598
            && $this->canAccessObject('show', $object)
2599
            && \count($this->getShow()) > 0
2600
            && $this->hasRoute('show')
2601
        ) {
2602
            $buttonList['show'] = [
2603
                'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2604
            ];
2605
        }
2606
2607
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
2608
            && $this->hasAccess('list')
2609
            && $this->hasRoute('list')
2610
        ) {
2611
            $buttonList['list'] = [
2612
                'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2613
            ];
2614
        }
2615
2616
        $buttonList = $this->configureActionButtons($buttonList, $action, $object);
2617
2618
        foreach ($this->getExtensions() as $extension) {
2619
            $buttonList = $extension->configureActionButtons($this, $buttonList, $action, $object);
2620
        }
2621
2622
        return $buttonList;
2623
    }
2624
2625
    /**
2626
     * {@inheritdoc}
2627
     */
2628
    public function getDashboardActions(): array
2629
    {
2630
        $actions = [];
2631
2632
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2633
            $actions['create'] = [
2634
                'label' => 'link_add',
2635
                'translation_domain' => 'SonataAdminBundle',
2636
                'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2637
                'url' => $this->generateUrl('create'),
2638
                'icon' => 'plus-circle',
2639
            ];
2640
        }
2641
2642
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2643
            $actions['list'] = [
2644
                'label' => 'link_list',
2645
                'translation_domain' => 'SonataAdminBundle',
2646
                'url' => $this->generateUrl('list'),
2647
                'icon' => 'list',
2648
            ];
2649
        }
2650
2651
        return $actions;
2652
    }
2653
2654
    /**
2655
     * {@inheritdoc}
2656
     */
2657
    final public function showMosaicButton($isShown): void
2658
    {
2659
        if ($isShown) {
2660
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
2661
        } else {
2662
            unset($this->listModes['mosaic']);
2663
        }
2664
    }
2665
2666
    final public function getSearchResultLink(object $object): ?string
2667
    {
2668
        foreach ($this->searchResultActions as $action) {
2669
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2670
                return $this->generateObjectUrl($action, $object);
2671
            }
2672
        }
2673
2674
        return null;
2675
    }
2676
2677
    /**
2678
     * NEXT_MAJOR: remove this method.
2679
     *
2680
     * Checks if a filter type is set to a default value.
2681
     */
2682
    final public function isDefaultFilter(string $name): bool
2683
    {
2684
        @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...
2685
            'Method "%s" is deprecated since sonata-project/admin-bundle 3.x.',
2686
            __METHOD__
2687
        ), E_USER_DEPRECATED);
2688
2689
        $filter = $this->getFilterParameters();
2690
        $default = $this->getDefaultFilterValues();
2691
2692
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
2693
            return false;
2694
        }
2695
2696
        return $filter[$name] === $default[$name];
2697
    }
2698
2699
    public function canAccessObject(string $action, ?object $object = null): bool
2700
    {
2701
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2702
    }
2703
2704
    public function configureActionButtons(array $buttonList, string $action, ?object $object = null): array
2705
    {
2706
        return $buttonList;
2707
    }
2708
2709
    /**
2710
     * Hook to run after initialization.
2711
     */
2712
    protected function configure(): void
2713
    {
2714
    }
2715
2716
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
2717
    {
2718
        return $query;
2719
    }
2720
2721
    /**
2722
     * urlize the given word.
2723
     *
2724
     * @param string $sep the separator
2725
     */
2726
    final protected function urlize(string $word, string $sep = '_'): string
2727
    {
2728
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
2729
    }
2730
2731
    final protected function getTemplateRegistry(): MutableTemplateRegistryInterface
2732
    {
2733
        return $this->templateRegistry;
2734
    }
2735
2736
    /**
2737
     * Returns a list of default sort values.
2738
     *
2739
     * @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...
2740
     */
2741
    final protected function getDefaultSortValues(): array
2742
    {
2743
        $defaultSortValues = [];
2744
2745
        $this->configureDefaultSortValues($defaultSortValues);
2746
2747
        foreach ($this->getExtensions() as $extension) {
2748
            // NEXT_MAJOR: remove method check
2749
            if (method_exists($extension, 'configureDefaultSortValues')) {
2750
                $extension->configureDefaultSortValues($this, $defaultSortValues);
2751
            }
2752
        }
2753
2754
        return $defaultSortValues;
2755
    }
2756
2757
    /**
2758
     * Returns a list of default filters.
2759
     */
2760
    final protected function getDefaultFilterValues(): array
2761
    {
2762
        $defaultFilterValues = [];
2763
2764
        $this->configureDefaultFilterValues($defaultFilterValues);
2765
2766
        foreach ($this->getExtensions() as $extension) {
2767
            // NEXT_MAJOR: remove method check
2768
            if (method_exists($extension, 'configureDefaultFilterValues')) {
2769
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2770
            }
2771
        }
2772
2773
        return $defaultFilterValues;
2774
    }
2775
2776
    protected function configureFormFields(FormMapper $form): void
2777
    {
2778
    }
2779
2780
    protected function configureListFields(ListMapper $list): void
2781
    {
2782
    }
2783
2784
    protected function configureDatagridFilters(DatagridMapper $filter): void
2785
    {
2786
    }
2787
2788
    protected function configureShowFields(ShowMapper $show): void
2789
    {
2790
    }
2791
2792
    protected function configureRoutes(RouteCollection $collection): void
2793
    {
2794
    }
2795
2796
    /**
2797
     * Allows you to customize batch actions.
2798
     *
2799
     * @param array $actions List of actions
2800
     */
2801
    protected function configureBatchActions(array $actions): array
2802
    {
2803
        return $actions;
2804
    }
2805
2806
    /**
2807
     * NEXT_MAJOR: remove this method.
2808
     *
2809
     * @deprecated Use configureTabMenu instead
2810
     */
2811
    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...
2812
    {
2813
    }
2814
2815
    /**
2816
     * Configures the tab menu in your admin.
2817
     */
2818
    protected function configureTabMenu(ItemInterface $menu, string $action, ?AdminInterface $childAdmin = null): void
2819
    {
2820
        // Use configureSideMenu not to mess with previous overrides
2821
        // NEXT_MAJOR: remove this line
2822
        $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...
2823
    }
2824
2825
    /**
2826
     * build the view FieldDescription array.
2827
     */
2828
    protected function buildShow(): void
2829
    {
2830
        if ($this->loaded['show']) {
2831
            return;
2832
        }
2833
2834
        $this->loaded['show'] = true;
2835
2836
        $this->show = $this->getShowBuilder()->getBaseList();
2837
        $mapper = new ShowMapper($this->getShowBuilder(), $this->show, $this);
2838
2839
        $this->configureShowFields($mapper);
2840
2841
        foreach ($this->getExtensions() as $extension) {
2842
            $extension->configureShowFields($mapper);
2843
        }
2844
    }
2845
2846
    /**
2847
     * build the list FieldDescription array.
2848
     */
2849
    protected function buildList(): void
2850
    {
2851
        if ($this->loaded['list']) {
2852
            return;
2853
        }
2854
2855
        $this->loaded['list'] = true;
2856
2857
        $this->list = $this->getListBuilder()->getBaseList();
2858
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
2859
2860
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
2861
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2862
                $this->getClass(),
2863
                'batch',
2864
                [
2865
                    'label' => 'batch',
2866
                    'code' => '_batch',
2867
                    'sortable' => false,
2868
                    'virtual_field' => true,
2869
                ]
2870
            );
2871
2872
            $fieldDescription->setAdmin($this);
2873
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
2874
2875
            $mapper->add($fieldDescription, ListMapper::TYPE_BATCH);
2876
        }
2877
2878
        $this->configureListFields($mapper);
2879
2880
        foreach ($this->getExtensions() as $extension) {
2881
            $extension->configureListFields($mapper);
2882
        }
2883
2884
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
2885
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2886
                $this->getClass(),
2887
                'select',
2888
                [
2889
                    'label' => false,
2890
                    'code' => '_select',
2891
                    'sortable' => false,
2892
                    'virtual_field' => false,
2893
                ]
2894
            );
2895
2896
            $fieldDescription->setAdmin($this);
2897
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
2898
2899
            $mapper->add($fieldDescription, ListMapper::TYPE_SELECT);
2900
        }
2901
    }
2902
2903
    /**
2904
     * Build the form FieldDescription collection.
2905
     */
2906
    protected function buildForm(): void
2907
    {
2908
        if ($this->loaded['form']) {
2909
            return;
2910
        }
2911
2912
        $this->loaded['form'] = true;
2913
2914
        $formBuilder = $this->getFormBuilder();
2915
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
2916
            $this->preValidate($event->getData());
2917
        }, 100);
2918
2919
        $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...
2920
    }
2921
2922
    /**
2923
     * Gets the subclass corresponding to the given name.
2924
     *
2925
     * @param string $name The name of the sub class
2926
     *
2927
     * @return string the subclass
2928
     */
2929
    protected function getSubClass(string $name): string
2930
    {
2931
        if ($this->hasSubClass($name)) {
2932
            return $this->subClasses[$name];
2933
        }
2934
2935
        // NEXT_MAJOR: Throw \LogicException instead.
2936
        throw new \RuntimeException(sprintf('Unable to find the subclass `%s` for admin `%s`', $name, static::class));
2937
    }
2938
2939
    /**
2940
     * Attach the inline validator to the model metadata, this must be done once per admin.
2941
     */
2942
    protected function attachInlineValidator(): void
2943
    {
2944
        $admin = $this;
2945
2946
        // add the custom inline validation option
2947
        $metadata = $this->validator->getMetadataFor($this->getClass());
2948
        if (!$metadata instanceof GenericMetadata) {
2949
            throw new \UnexpectedValueException(
2950
                sprintf(
2951
                    'Cannot add inline validator for %s because its metadata is an instance of %s instead of %s',
2952
                    $this->getClass(),
2953
                    \get_class($metadata),
2954
                    GenericMetadata::class
2955
                )
2956
            );
2957
        }
2958
2959
        $metadata->addConstraint(new InlineConstraint([
2960
            'service' => $this,
2961
            'method' => static function (ErrorElement $errorElement, $object) use ($admin): void {
2962
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
2963
2964
                // This avoid the main validation to be cascaded to children
2965
                // The problem occurs when a model Page has a collection of Page as property
2966
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
2967
                    return;
2968
                }
2969
2970
                $admin->validate($errorElement, $object);
2971
2972
                foreach ($admin->getExtensions() as $extension) {
2973
                    $extension->validate($admin, $errorElement, $object);
2974
                }
2975
            },
2976
            'serializingWarning' => true,
2977
        ]));
2978
    }
2979
2980
    /**
2981
     * NEXT_MAJOR: Remove this function.
2982
     *
2983
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
2984
     *
2985
     * Predefine per page options.
2986
     */
2987
    protected function predefinePerPageOptions(): void
2988
    {
2989
        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...
2990
        $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...
2991
        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...
2992
    }
2993
2994
    /**
2995
     * Return list routes with permissions name.
2996
     *
2997
     * @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...
2998
     */
2999
    protected function getAccess(): array
3000
    {
3001
        $access = array_merge([
3002
            'acl' => 'MASTER',
3003
            'export' => 'EXPORT',
3004
            'historyCompareRevisions' => 'EDIT',
3005
            'historyViewRevision' => 'EDIT',
3006
            'history' => 'EDIT',
3007
            'edit' => 'EDIT',
3008
            'show' => 'VIEW',
3009
            'create' => 'CREATE',
3010
            'delete' => 'DELETE',
3011
            'batchDelete' => 'DELETE',
3012
            'list' => 'LIST',
3013
        ], $this->getAccessMapping());
3014
3015
        foreach ($this->extensions as $extension) {
3016
            $access = array_merge($access, $extension->getAccessMapping($this));
3017
        }
3018
3019
        return $access;
3020
    }
3021
3022
    /**
3023
     * Configures a list of default filters.
3024
     */
3025
    protected function configureDefaultFilterValues(array &$filterValues): void
3026
    {
3027
    }
3028
3029
    /**
3030
     * Configures a list of default sort values.
3031
     *
3032
     * Example:
3033
     *   $sortValues['_sort_by'] = 'foo'
3034
     *   $sortValues['_sort_order'] = 'DESC'
3035
     */
3036
    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...
3037
    {
3038
    }
3039
3040
    /**
3041
     * Set the parent object, if any, to the provided object.
3042
     */
3043
    final protected function appendParentObject(object $object): void
3044
    {
3045
        if ($this->isChild() && $this->getParentAssociationMapping()) {
3046
            $parentAdmin = $this->getParent();
3047
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
3048
3049
            if (null !== $parentObject) {
3050
                $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3051
                $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3052
3053
                $value = $propertyAccessor->getValue($object, $propertyPath);
3054
3055
                if (\is_array($value) || $value instanceof \ArrayAccess) {
3056
                    $value[] = $parentObject;
3057
                    $propertyAccessor->setValue($object, $propertyPath, $value);
3058
                } else {
3059
                    $propertyAccessor->setValue($object, $propertyPath, $parentObject);
3060
                }
3061
            }
3062
        } elseif ($this->hasParentFieldDescription()) {
3063
            $parentAdmin = $this->getParentFieldDescription()->getAdmin();
3064
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
3065
3066
            if (null !== $parentObject) {
3067
                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...
3068
            }
3069
        }
3070
    }
3071
3072
    /**
3073
     * {@inheritdoc}
3074
     */
3075
    private function buildDatagrid(): void
3076
    {
3077
        if ($this->loaded['datagrid']) {
3078
            return;
3079
        }
3080
3081
        $this->loaded['datagrid'] = true;
3082
3083
        $filterParameters = $this->getFilterParameters();
3084
3085
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
3086
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
3087
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
3088
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
3089
            } else {
3090
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
3091
                    $this->getClass(),
3092
                    $filterParameters['_sort_by'],
3093
                    []
3094
                );
3095
3096
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
3097
            }
3098
        }
3099
3100
        // initialize the datagrid
3101
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
3102
3103
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
3104
3105
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
3106
3107
        // build the datagrid filter
3108
        $this->configureDatagridFilters($mapper);
3109
3110
        // ok, try to limit to add parent filter
3111
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
3112
            $mapper->add($this->getParentAssociationMapping(), null, [
3113
                'show_filter' => false,
3114
                'label' => false,
3115
                'field_type' => ModelHiddenType::class,
3116
                'field_options' => [
3117
                    'model_manager' => $this->getModelManager(),
3118
                ],
3119
                'operator_type' => HiddenType::class,
3120
            ], null, null, [
3121
                'admin_code' => $this->getParent()->getCode(),
3122
            ]);
3123
        }
3124
3125
        foreach ($this->getExtensions() as $extension) {
3126
            $extension->configureDatagridFilters($mapper);
3127
        }
3128
    }
3129
3130
    /**
3131
     * Build all the related urls to the current admin.
3132
     */
3133
    private function buildRoutes(): void
3134
    {
3135
        if ($this->loaded['routes']) {
3136
            return;
3137
        }
3138
3139
        $this->loaded['routes'] = true;
3140
3141
        $this->routes = new RouteCollection(
3142
            $this->getBaseCodeRoute(),
3143
            $this->getBaseRouteName(),
3144
            $this->getBaseRoutePattern(),
3145
            $this->getBaseControllerName()
3146
        );
3147
3148
        $this->routeBuilder->build($this, $this->routes);
3149
3150
        $this->configureRoutes($this->routes);
3151
3152
        foreach ($this->getExtensions() as $extension) {
3153
            $extension->configureRoutes($this, $this->routes);
3154
        }
3155
    }
3156
}
3157
3158
class_exists(ErrorElement::class);
3159