Completed
Pull Request — master (#6093)
by Mathieu
35:29
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\Model\ModelManagerInterface;
33
use Sonata\AdminBundle\Object\Metadata;
34
use Sonata\AdminBundle\Object\MetadataInterface;
35
use Sonata\AdminBundle\Route\RouteCollection;
36
use Sonata\AdminBundle\Route\RouteGeneratorInterface;
37
use Sonata\AdminBundle\Security\Handler\AclSecurityHandlerInterface;
38
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
39
use Sonata\AdminBundle\Show\ShowMapper;
40
use Sonata\AdminBundle\Templating\MutableTemplateRegistryInterface;
41
use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface;
42
use Sonata\Exporter\Source\SourceIteratorInterface;
43
use Sonata\Form\Validator\Constraints\InlineConstraint;
44
use Sonata\Form\Validator\ErrorElement;
45
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
46
use Symfony\Component\Form\Form;
47
use Symfony\Component\Form\FormBuilderInterface;
48
use Symfony\Component\Form\FormEvent;
49
use Symfony\Component\Form\FormEvents;
50
use Symfony\Component\Form\FormInterface;
51
use Symfony\Component\HttpFoundation\Request;
52
use Symfony\Component\PropertyAccess\PropertyPath;
53
use Symfony\Component\Routing\Generator\UrlGeneratorInterface as RoutingUrlGeneratorInterface;
54
use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
55
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
56
use Symfony\Component\Validator\Validator\ValidatorInterface;
57
use Symfony\Contracts\Translation\TranslatorInterface;
58
59
/**
60
 * @author Thomas Rabaix <[email protected]>
61
 */
62
abstract class AbstractAdmin implements AdminInterface, DomainObjectInterface, AdminTreeInterface
63
{
64
    public const CONTEXT_MENU = 'menu';
65
    public const CONTEXT_DASHBOARD = 'dashboard';
66
67
    public const CLASS_REGEX =
68
        '@
69
        (?:([A-Za-z0-9]*)\\\)?        # vendor name / app name
70
        (Bundle\\\)?                  # optional bundle directory
71
        ([A-Za-z0-9]+?)(?:Bundle)?\\\ # bundle name, with optional suffix
72
        (
73
            Entity|Document|Model|PHPCR|CouchDocument|Phpcr|
74
            Doctrine\\\Orm|Doctrine\\\Phpcr|Doctrine\\\MongoDB|Doctrine\\\CouchDB
75
        )\\\(.*)@x';
76
77
    public const MOSAIC_ICON_CLASS = 'fa fa-th-large fa-fw';
78
79
    /**
80
     * The list FieldDescription constructed from the configureListField method.
81
     *
82
     * @var FieldDescriptionInterface[]
83
     */
84
    protected $listFieldDescriptions = [];
85
86
    /**
87
     * The show FieldDescription constructed from the configureShowFields method.
88
     *
89
     * @var FieldDescriptionInterface[]
90
     */
91
    protected $showFieldDescriptions = [];
92
93
    /**
94
     * The list FieldDescription constructed from the configureFormField method.
95
     *
96
     * @var FieldDescriptionInterface[]
97
     */
98
    protected $formFieldDescriptions = [];
99
100
    /**
101
     * The filter FieldDescription constructed from the configureFilterField method.
102
     *
103
     * @var FieldDescriptionInterface[]
104
     */
105
    protected $filterFieldDescriptions = [];
106
107
    /**
108
     * NEXT_MAJOR: Remove this property.
109
     *
110
     * The number of result to display in the list.
111
     *
112
     * @deprecated since sonata-project/admin-bundle 3.67.
113
     *
114
     * @var int
115
     */
116
    protected $maxPerPage = 32;
117
118
    /**
119
     * The maximum number of page numbers to display in the list.
120
     *
121
     * @var int
122
     */
123
    protected $maxPageLinks = 25;
124
125
    /**
126
     * The base route name used to generate the routing information.
127
     *
128
     * @var string
129
     */
130
    protected $baseRouteName;
131
132
    /**
133
     * The base route pattern used to generate the routing information.
134
     *
135
     * @var string
136
     */
137
    protected $baseRoutePattern;
138
139
    /**
140
     * The base name controller used to generate the routing information.
141
     *
142
     * @var string
143
     */
144
    protected $baseControllerName;
145
146
    /**
147
     * The label class name  (used in the title/breadcrumb ...).
148
     *
149
     * @var string
150
     */
151
    protected $classnameLabel;
152
153
    /**
154
     * The translation domain to be used to translate messages.
155
     *
156
     * @var string
157
     */
158
    protected $translationDomain = 'messages';
159
160
    /**
161
     * Options to set to the form (ie, validation_groups).
162
     *
163
     * @var array
164
     */
165
    protected $formOptions = [];
166
167
    /**
168
     * NEXT_MAJOR: Remove this property.
169
     *
170
     * Default values to the datagrid.
171
     *
172
     * @deprecated since sonata-project/admin-bundle 3.67, use configureDefaultSortValues() instead.
173
     *
174
     * @var array
175
     */
176
    protected $datagridValues = [
177
        '_page' => 1,
178
        '_per_page' => 32,
179
    ];
180
181
    /**
182
     * NEXT_MAJOR: Remove this property.
183
     *
184
     * Predefined per page options.
185
     *
186
     * @deprecated since sonata-project/admin-bundle 3.67.
187
     *
188
     * @var array
189
     */
190
    protected $perPageOptions = [16, 32, 64, 128, 256];
191
192
    /**
193
     * Pager type.
194
     *
195
     * @var string
196
     */
197
    protected $pagerType = Pager::TYPE_DEFAULT;
198
199
    /**
200
     * The code related to the admin.
201
     *
202
     * @var string
203
     */
204
    protected $code;
205
206
    /**
207
     * The label.
208
     *
209
     * @var string
210
     */
211
    protected $label;
212
213
    /**
214
     * Whether or not to persist the filters in the session.
215
     *
216
     * NEXT_MAJOR: remove this property
217
     *
218
     * @var bool
219
     *
220
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
221
     */
222
    protected $persistFilters = false;
223
224
    /**
225
     * Array of routes related to this admin.
226
     *
227
     * @var RouteCollection
228
     */
229
    protected $routes;
230
231
    /**
232
     * The subject only set in edit/update/create mode.
233
     *
234
     * @var object|null
235
     */
236
    protected $subject;
237
238
    /**
239
     * Define a Collection of child admin, ie /admin/order/{id}/order-element/{childId}.
240
     *
241
     * @var array
242
     */
243
    protected $children = [];
244
245
    /**
246
     * Reference the parent admin.
247
     *
248
     * @var AdminInterface|null
249
     */
250
    protected $parent;
251
252
    /**
253
     * The related parent association, ie if OrderElement has a parent property named order,
254
     * then the $parentAssociationMapping must be a string named `order`.
255
     *
256
     * NEXT_MAJOR: remove this attribute.
257
     *
258
     * @deprecated This attribute is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0
259
     *
260
     * @var string
261
     */
262
    protected $baseCodeRoute = '';
263
264
    /**
265
     * NEXT_MAJOR: should be default array and private.
266
     *
267
     * @var string|array
268
     */
269
    protected $parentAssociationMapping;
270
271
    /**
272
     * Reference the parent FieldDescription related to this admin
273
     * only set for FieldDescription which is associated to an Sub Admin instance.
274
     *
275
     * @var FieldDescriptionInterface
276
     */
277
    protected $parentFieldDescription;
278
279
    /**
280
     * If true then the current admin is part of the nested admin set (from the url).
281
     *
282
     * @var bool
283
     */
284
    protected $currentChild = false;
285
286
    /**
287
     * The uniqid is used to avoid clashing with 2 admin related to the code
288
     * ie: a Block linked to a Block.
289
     *
290
     * @var string
291
     */
292
    protected $uniqid;
293
294
    /**
295
     * The Entity or Document manager.
296
     *
297
     * @var ModelManagerInterface
298
     */
299
    protected $modelManager;
300
301
    /**
302
     * The current request object.
303
     *
304
     * @var Request|null
305
     */
306
    protected $request;
307
308
    /**
309
     * The translator component.
310
     *
311
     * NEXT_MAJOR: remove this property
312
     *
313
     * @var TranslatorInterface
314
     *
315
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
316
     */
317
    protected $translator;
318
319
    /**
320
     * The related form contractor.
321
     *
322
     * @var FormContractorInterface
323
     */
324
    protected $formContractor;
325
326
    /**
327
     * The related list builder.
328
     *
329
     * @var ListBuilderInterface
330
     */
331
    protected $listBuilder;
332
333
    /**
334
     * The related view builder.
335
     *
336
     * @var ShowBuilderInterface
337
     */
338
    protected $showBuilder;
339
340
    /**
341
     * The related datagrid builder.
342
     *
343
     * @var DatagridBuilderInterface
344
     */
345
    protected $datagridBuilder;
346
347
    /**
348
     * @var RouteBuilderInterface
349
     */
350
    protected $routeBuilder;
351
352
    /**
353
     * The datagrid instance.
354
     *
355
     * @var DatagridInterface|null
356
     */
357
    protected $datagrid;
358
359
    /**
360
     * The router instance.
361
     *
362
     * @var RouteGeneratorInterface|null
363
     */
364
    protected $routeGenerator;
365
366
    /**
367
     * @var SecurityHandlerInterface
368
     */
369
    protected $securityHandler;
370
371
    /**
372
     * @var ValidatorInterface
373
     */
374
    protected $validator;
375
376
    /**
377
     * The configuration pool.
378
     *
379
     * @var Pool
380
     */
381
    protected $configurationPool;
382
383
    /**
384
     * @var ItemInterface
385
     */
386
    protected $menu;
387
388
    /**
389
     * @var FactoryInterface
390
     */
391
    protected $menuFactory;
392
393
    /**
394
     * @var array<string, bool>
395
     */
396
    protected $loaded = [
397
        'view_fields' => false, // NEXT_MAJOR: Remove this unused value.
398
        'view_groups' => false, // NEXT_MAJOR: Remove this unused value.
399
        'routes' => false,
400
        'tab_menu' => false,
401
        'show' => false,
402
        'list' => false,
403
        'form' => false,
404
        'datagrid' => false,
405
    ];
406
407
    /**
408
     * @var string[]
409
     */
410
    protected $formTheme = [];
411
412
    /**
413
     * @var string[]
414
     */
415
    protected $filterTheme = [];
416
417
    /**
418
     * @var AdminExtensionInterface[]
419
     */
420
    protected $extensions = [];
421
422
    /**
423
     * @var LabelTranslatorStrategyInterface
424
     */
425
    protected $labelTranslatorStrategy;
426
427
    /**
428
     * Setting to true will enable preview mode for
429
     * the entity and show a preview button in the
430
     * edit/create forms.
431
     *
432
     * @var bool
433
     */
434
    protected $supportsPreviewMode = false;
435
436
    /**
437
     * Roles and permissions per role.
438
     *
439
     * @var array 'role' => ['permission', 'permission']
440
     */
441
    protected $securityInformation = [];
442
443
    protected $cacheIsGranted = [];
444
445
    /**
446
     * Action list for the search result.
447
     *
448
     * @var string[]
449
     */
450
    protected $searchResultActions = ['edit', 'show'];
451
452
    protected $listModes = [
453
        'list' => [
454
            'class' => 'fa fa-list fa-fw',
455
        ],
456
        'mosaic' => [
457
            'class' => self::MOSAIC_ICON_CLASS,
458
        ],
459
    ];
460
461
    /**
462
     * The Access mapping.
463
     *
464
     * @var array [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
465
     */
466
    protected $accessMapping = [];
467
468
    /**
469
     * @var MutableTemplateRegistryInterface
470
     */
471
    private $templateRegistry;
472
473
    /**
474
     * The class name managed by the admin class.
475
     *
476
     * @var string
477
     */
478
    private $class;
479
480
    /**
481
     * The subclasses supported by the admin class.
482
     *
483
     * @var array<string, string>
484
     */
485
    private $subClasses = [];
486
487
    /**
488
     * The list collection.
489
     *
490
     * @var FieldDescriptionCollection|null
491
     */
492
    private $list;
493
494
    /**
495
     * @var FieldDescriptionCollection|null
496
     */
497
    private $show;
498
499
    /**
500
     * @var Form|null
501
     */
502
    private $form;
503
504
    /**
505
     * The cached base route name.
506
     *
507
     * @var string
508
     */
509
    private $cachedBaseRouteName;
510
511
    /**
512
     * The cached base route pattern.
513
     *
514
     * @var string
515
     */
516
    private $cachedBaseRoutePattern;
517
518
    /**
519
     * The form group disposition.
520
     *
521
     * @var array<string, mixed>
522
     */
523
    private $formGroups = [];
524
525
    /**
526
     * The form tabs disposition.
527
     *
528
     * @var array<string, mixed>
529
     */
530
    private $formTabs = [];
531
532
    /**
533
     * The view group disposition.
534
     *
535
     * @var array<string, mixed>
536
     */
537
    private $showGroups = [];
538
539
    /**
540
     * The view tab disposition.
541
     *
542
     * @var array<string, mixed>
543
     */
544
    private $showTabs = [];
545
546
    /**
547
     * The manager type to use for the admin.
548
     *
549
     * @var string
550
     */
551
    private $managerType;
552
553
    /**
554
     * Component responsible for persisting filters.
555
     *
556
     * @var FilterPersisterInterface|null
557
     */
558
    private $filterPersister;
559
560
    public function __construct(string $code, string $class, ?string $baseControllerName = null)
561
    {
562
        $this->code = $code;
563
        $this->class = $class;
564
        $this->baseControllerName = $baseControllerName;
565
566
        // NEXT_MAJOR: Remove this line.
567
        $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...
568
569
        // NEXT_MAJOR: Remove this line.
570
        $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...
571
    }
572
573
    /**
574
     * {@inheritdoc}
575
     */
576
    public function getExportFormats(): array
577
    {
578
        return [
579
            'json', 'xml', 'csv', 'xls',
580
        ];
581
    }
582
583
    /**
584
     * {@inheritdoc}
585
     */
586
    public function getExportFields(): array
587
    {
588
        $fields = $this->getModelManager()->getExportFields($this->getClass());
589
590
        foreach ($this->getExtensions() as $extension) {
591
            if (method_exists($extension, 'configureExportFields')) {
592
                $fields = $extension->configureExportFields($this, $fields);
593
            }
594
        }
595
596
        return $fields;
597
    }
598
599
    public function getDataSourceIterator(): SourceIteratorInterface
600
    {
601
        $datagrid = $this->getDatagrid();
602
        $datagrid->buildPager();
603
604
        $fields = [];
605
606
        foreach ($this->getExportFields() as $key => $field) {
607
            $label = $this->getTranslationLabel($field, 'export', 'label');
608
            $transLabel = $this->trans($label);
609
610
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
611
            // No translation key exists
612
            if ($transLabel === $label) {
613
                $fields[$key] = $field;
614
            } else {
615
                $fields[$transLabel] = $field;
616
            }
617
        }
618
619
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
0 ignored issues
show
Bug introduced by
It seems like $datagrid defined by $this->getDatagrid() on line 601 can be null; however, Sonata\AdminBundle\Model...getDataSourceIterator() 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...
620
    }
621
622
    public function validate(ErrorElement $errorElement, $object): void
623
    {
624
    }
625
626
    /**
627
     * define custom variable.
628
     */
629
    public function initialize(): void
630
    {
631
        if (!$this->classnameLabel) {
632
            /* NEXT_MAJOR: remove cast to string, null is not supposed to be
633
            supported but was documented as such */
634
            $this->classnameLabel = substr(
635
                (string) $this->getClass(),
636
                strrpos((string) $this->getClass(), '\\') + 1
637
            );
638
        }
639
640
        $this->configure();
641
    }
642
643
    public function update(object $object): object
644
    {
645
        $this->preUpdate($object);
646
        foreach ($this->extensions as $extension) {
647
            $extension->preUpdate($this, $object);
648
        }
649
650
        $result = $this->getModelManager()->update($object);
651
        // BC compatibility
652
        if (null !== $result) {
653
            $object = $result;
654
        }
655
656
        $this->postUpdate($object);
657
        foreach ($this->extensions as $extension) {
658
            $extension->postUpdate($this, $object);
659
        }
660
661
        return $object;
662
    }
663
664
    public function create(object $object): object
665
    {
666
        $this->prePersist($object);
667
        foreach ($this->extensions as $extension) {
668
            $extension->prePersist($this, $object);
669
        }
670
671
        $result = $this->getModelManager()->create($object);
672
        // BC compatibility
673
        if (null !== $result) {
674
            $object = $result;
675
        }
676
677
        $this->postPersist($object);
678
        foreach ($this->extensions as $extension) {
679
            $extension->postPersist($this, $object);
680
        }
681
682
        $this->createObjectSecurity($object);
683
684
        return $object;
685
    }
686
687
    public function delete(object $object): void
688
    {
689
        $this->preRemove($object);
690
        foreach ($this->extensions as $extension) {
691
            $extension->preRemove($this, $object);
692
        }
693
694
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
695
        $this->getModelManager()->delete($object);
696
697
        $this->postRemove($object);
698
        foreach ($this->extensions as $extension) {
699
            $extension->postRemove($this, $object);
700
        }
701
    }
702
703
    public function preValidate(object $object): void
704
    {
705
    }
706
707
    public function preUpdate(object $object): void
708
    {
709
    }
710
711
    public function postUpdate(object $object): void
712
    {
713
    }
714
715
    public function prePersist(object $object): void
716
    {
717
    }
718
719
    public function postPersist(object $object): void
720
    {
721
    }
722
723
    public function preRemove(object $object): void
724
    {
725
    }
726
727
    public function postRemove(object $object): void
728
    {
729
    }
730
731
    public function preBatchAction(string $actionName, ProxyQueryInterface $query, array &$idx, bool $allElements = false): void
732
    {
733
    }
734
735
    public function getFilterParameters(): array
736
    {
737
        $parameters = [];
738
739
        // build the values array
740
        if ($this->hasRequest()) {
741
            $filters = $this->request->query->get('filter', []);
742
            if (isset($filters['_page'])) {
743
                $filters['_page'] = (int) $filters['_page'];
744
            }
745
            if (isset($filters['_per_page'])) {
746
                $filters['_per_page'] = (int) $filters['_per_page'];
747
            }
748
749
            // if filter persistence is configured
750
            // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
751
            if (false !== $this->persistFilters && null !== $this->filterPersister) {
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$persistFilters has been deprecated with message: since 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...
752
                // if reset filters is asked, remove from storage
753
                if ('reset' === $this->request->query->get('filters')) {
754
                    $this->filterPersister->reset($this->getCode());
755
                }
756
757
                // if no filters, fetch from storage
758
                // otherwise save to storage
759
                if (empty($filters)) {
760
                    $filters = $this->filterPersister->get($this->getCode());
761
                } else {
762
                    $this->filterPersister->set($this->getCode(), $filters);
763
                }
764
            }
765
766
            $parameters = array_merge(
767
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
768
                $this->datagridValues, // 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...
769
                $this->getDefaultSortValues(),
770
                $this->getDefaultFilterValues(),
771
                $filters
772
            );
773
774
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
775
                $parameters['_per_page'] = $this->getMaxPerPage();
776
            }
777
778
            // always force the parent value
779
            if ($this->isChild() && $this->getParentAssociationMapping()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getParentAssociationMapping() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
780
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
781
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
782
            }
783
        }
784
785
        return $parameters;
786
    }
787
788
    /**
789
     * Returns the name of the parent related field, so the field can be use to set the default
790
     * value (ie the parent object) or to filter the object.
791
     *
792
     * @throws \InvalidArgumentException
793
     *
794
     * @return string|null
795
     */
796
    public function getParentAssociationMapping()
797
    {
798
        // NEXT_MAJOR: remove array check
799
        if (\is_array($this->parentAssociationMapping) && $this->isChild()) {
800
            $parent = $this->getParent()->getCode();
801
802
            if (\array_key_exists($parent, $this->parentAssociationMapping)) {
803
                return $this->parentAssociationMapping[$parent];
804
            }
805
806
            throw new \InvalidArgumentException(sprintf(
807
                "There's no association between %s and %s.",
808
                $this->getCode(),
809
                $this->getParent()->getCode()
810
            ));
811
        }
812
813
        // NEXT_MAJOR: remove this line
814
        return $this->parentAssociationMapping;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->parentAssociationMapping; of type string|array adds the type array to the return on line 814 which is incompatible with the return type documented by Sonata\AdminBundle\Admin...arentAssociationMapping of type string|null.
Loading history...
815
    }
816
817
    final public function addParentAssociationMapping(string $code, string $value): void
818
    {
819
        $this->parentAssociationMapping[$code] = $value;
820
    }
821
822
    /**
823
     * Returns the baseRoutePattern used to generate the routing information.
824
     *
825
     * @throws \RuntimeException
826
     *
827
     * @return string the baseRoutePattern used to generate the routing information
828
     */
829
    public function getBaseRoutePattern(): string
830
    {
831
        if (null !== $this->cachedBaseRoutePattern) {
832
            return $this->cachedBaseRoutePattern;
833
        }
834
835
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
836
            $baseRoutePattern = $this->baseRoutePattern;
837
            if (!$this->baseRoutePattern) {
838
                preg_match(self::CLASS_REGEX, $this->class, $matches);
839
840
                if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
841
                    throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', static::class));
842
                }
843
                $baseRoutePattern = $this->urlize($matches[5], '-');
844
            }
845
846
            $this->cachedBaseRoutePattern = sprintf(
847
                '%s/%s/%s',
848
                $this->getParent()->getBaseRoutePattern(),
849
                $this->getParent()->getRouterIdParameter(),
850
                $baseRoutePattern
851
            );
852
        } elseif ($this->baseRoutePattern) {
853
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
854
        } else {
855
            preg_match(self::CLASS_REGEX, $this->class, $matches);
856
857
            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...
858
                throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', static::class));
859
            }
860
861
            $this->cachedBaseRoutePattern = sprintf(
862
                '/%s%s/%s',
863
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
864
                $this->urlize($matches[3], '-'),
865
                $this->urlize($matches[5], '-')
866
            );
867
        }
868
869
        return $this->cachedBaseRoutePattern;
870
    }
871
872
    /**
873
     * Returns the baseRouteName used to generate the routing information.
874
     *
875
     * @throws \RuntimeException
876
     *
877
     * @return string the baseRouteName used to generate the routing information
878
     */
879
    public function getBaseRouteName(): string
880
    {
881
        if (null !== $this->cachedBaseRouteName) {
882
            return $this->cachedBaseRouteName;
883
        }
884
885
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
886
            $baseRouteName = $this->baseRouteName;
887
            if (!$this->baseRouteName) {
888
                preg_match(self::CLASS_REGEX, $this->class, $matches);
889
890
                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...
891
                    throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', static::class));
892
                }
893
                $baseRouteName = $this->urlize($matches[5]);
894
            }
895
896
            $this->cachedBaseRouteName = sprintf(
897
                '%s_%s',
898
                $this->getParent()->getBaseRouteName(),
899
                $baseRouteName
900
            );
901
        } elseif ($this->baseRouteName) {
902
            $this->cachedBaseRouteName = $this->baseRouteName;
903
        } else {
904
            preg_match(self::CLASS_REGEX, $this->class, $matches);
905
906
            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...
907
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', static::class));
908
            }
909
910
            $this->cachedBaseRouteName = sprintf(
911
                'admin_%s%s_%s',
912
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
913
                $this->urlize($matches[3]),
914
                $this->urlize($matches[5])
915
            );
916
        }
917
918
        return $this->cachedBaseRouteName;
919
    }
920
921
    public function getClass(): string
922
    {
923
        if ($this->hasActiveSubClass()) {
924
            if ($this->hasParentFieldDescription()) {
925
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
926
            }
927
928
            $subClass = $this->getRequest()->query->get('subclass');
929
930
            if (!$this->hasSubClass($subClass)) {
931
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
932
            }
933
934
            return $this->getSubClass($subClass);
935
        }
936
937
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
938
        if ($this->subject && \is_object($this->subject)) {
939
            return ClassUtils::getClass($this->subject);
940
        }
941
942
        return $this->class;
943
    }
944
945
    public function getSubClasses(): array
946
    {
947
        return $this->subClasses;
948
    }
949
950
    /**
951
     * NEXT_MAJOR: remove this method.
952
     */
953
    public function addSubClass($subClass): void
954
    {
955
        @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...
956
            'Method "%s" is deprecated since sonata-project/admin-bundle 3.30 and will be removed in 4.0.',
957
            __METHOD__
958
        ), E_USER_DEPRECATED);
959
960
        if (!\in_array($subClass, $this->subClasses, true)) {
961
            $this->subClasses[] = $subClass;
962
        }
963
    }
964
965
    public function setSubClasses(array $subClasses): void
966
    {
967
        $this->subClasses = $subClasses;
968
    }
969
970
    public function hasSubClass(string $name): bool
971
    {
972
        return isset($this->subClasses[$name]);
973
    }
974
975
    public function hasActiveSubClass(): bool
976
    {
977
        if (\count($this->subClasses) > 0 && $this->request) {
978
            return null !== $this->getRequest()->query->get('subclass');
979
        }
980
981
        return false;
982
    }
983
984
    public function getActiveSubClass(): string
985
    {
986
        if (!$this->hasActiveSubClass()) {
987
            throw new \LogicException(sprintf(
988
                'Admin "%s" has no active subclass.',
989
                static::class
990
            ));
991
        }
992
993
        return $this->getSubClass($this->getActiveSubclassCode());
994
    }
995
996
    public function getActiveSubclassCode(): string
997
    {
998
        if (!$this->hasActiveSubClass()) {
999
            throw new \LogicException(sprintf(
1000
                'Admin "%s" has no active subclass.',
1001
                static::class
1002
            ));
1003
        }
1004
1005
        $subClass = $this->getRequest()->query->get('subclass');
1006
1007
        if (!$this->hasSubClass($subClass)) {
1008
            throw new \LogicException(sprintf(
1009
                'Admin "%s" has no active subclass.',
1010
                static::class
1011
            ));
1012
        }
1013
1014
        return $subClass;
1015
    }
1016
1017
    public function getBatchActions(): array
1018
    {
1019
        $actions = [];
1020
1021
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1022
            $actions['delete'] = [
1023
                'label' => 'action_delete',
1024
                'translation_domain' => 'SonataAdminBundle',
1025
                'ask_confirmation' => true, // by default always true
1026
            ];
1027
        }
1028
1029
        $actions = $this->configureBatchActions($actions);
1030
1031
        foreach ($this->getExtensions() as $extension) {
1032
            $actions = $extension->configureBatchActions($this, $actions);
1033
        }
1034
1035
        foreach ($actions  as $name => &$action) {
1036
            if (!\array_key_exists('label', $action)) {
1037
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1038
            }
1039
1040
            if (!\array_key_exists('translation_domain', $action)) {
1041
                $action['translation_domain'] = $this->getTranslationDomain();
1042
            }
1043
        }
1044
1045
        return $actions;
1046
    }
1047
1048
    /**
1049
     * NEXT_MAJOR: Create a `RouteCollectionInterface` and use as return type.
1050
     */
1051
    public function getRoutes(): RouteCollection
1052
    {
1053
        $this->buildRoutes();
1054
1055
        return $this->routes;
1056
    }
1057
1058
    public function getRouterIdParameter(): string
1059
    {
1060
        return '{'.$this->getIdParameter().'}';
1061
    }
1062
1063
    public function getIdParameter(): string
1064
    {
1065
        $parameter = 'id';
1066
1067
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1068
            $parameter = 'child'.ucfirst($parameter);
1069
        }
1070
1071
        return $parameter;
1072
    }
1073
1074
    public function hasRoute(string $name): bool
1075
    {
1076
        if (!$this->routeGenerator) {
1077
            throw new \RuntimeException('RouteGenerator cannot be null');
1078
        }
1079
1080
        return $this->routeGenerator->hasAdminRoute($this, $name);
1081
    }
1082
1083
    public function isCurrentRoute(string $name, ?string $adminCode = null): bool
1084
    {
1085
        if (!$this->hasRequest()) {
1086
            return false;
1087
        }
1088
1089
        $request = $this->getRequest();
1090
        $route = $request->get('_route');
1091
1092
        if ($adminCode) {
1093
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1094
        } else {
1095
            $admin = $this;
1096
        }
1097
1098
        if (!$admin) {
1099
            return false;
1100
        }
1101
1102
        return ($admin->getBaseRouteName().'_'.$name) === $route;
1103
    }
1104
1105
    public function generateObjectUrl(string $name, object $object, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1106
    {
1107
        $parameters['id'] = $this->getUrlSafeIdentifier($object);
1108
1109
        return $this->generateUrl($name, $parameters, $referenceType);
1110
    }
1111
1112
    public function generateUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1113
    {
1114
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $referenceType);
1115
    }
1116
1117
    public function generateMenuUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): array
1118
    {
1119
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $referenceType);
1120
    }
1121
1122
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry): void
1123
    {
1124
        $this->templateRegistry = $templateRegistry;
1125
    }
1126
1127
    /**
1128
     * @param array<string, string> $templates
1129
     */
1130
    public function setTemplates(array $templates): void
1131
    {
1132
        $this->getTemplateRegistry()->setTemplates($templates);
1133
    }
1134
1135
    /**
1136
     * {@inheritdoc}
1137
     */
1138
    public function setTemplate(string $name, string $template): void
1139
    {
1140
        $this->getTemplateRegistry()->setTemplate($name, $template);
1141
    }
1142
1143
    public function getNewInstance(): object
1144
    {
1145
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1146
        foreach ($this->getExtensions() as $extension) {
1147
            $extension->alterNewInstance($this, $object);
1148
        }
1149
1150
        return $object;
1151
    }
1152
1153
    public function getFormBuilder(): FormBuilderInterface
1154
    {
1155
        $this->formOptions['data_class'] = $this->getClass();
1156
1157
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1158
            $this->getUniqid(),
1159
            $this->formOptions
1160
        );
1161
1162
        $this->defineFormBuilder($formBuilder);
1163
1164
        return $formBuilder;
1165
    }
1166
1167
    /**
1168
     * This method is being called by the main admin class and the child class,
1169
     * the getFormBuilder is only call by the main admin class.
1170
     */
1171
    public function defineFormBuilder(FormBuilderInterface $formBuilder): void
1172
    {
1173
        if (!$this->hasSubject()) {
1174
            @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...
1175
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.65 and will throw an exception in 4.0. '.
1176
                'Use %s::setSubject() to set the subject.',
1177
                __METHOD__,
1178
                __CLASS__
1179
            ), E_USER_DEPRECATED);
1180
            // NEXT_MAJOR : remove the previous `trigger_error()` call and uncomment the following exception
1181
            // throw new \LogicException(sprintf(
1182
            //    'Admin "%s" has no subject.',
1183
            //    static::class
1184
            // ));
1185
        }
1186
1187
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1188
1189
        $this->configureFormFields($mapper);
1190
1191
        foreach ($this->getExtensions() as $extension) {
1192
            $extension->configureFormFields($mapper);
1193
        }
1194
1195
        $this->attachInlineValidator();
1196
    }
1197
1198
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription): void
1199
    {
1200
        $pool = $this->getConfigurationPool();
1201
1202
        $adminCode = $fieldDescription->getOption('admin_code');
1203
1204
        if (null !== $adminCode) {
1205
            if (!$pool->hasAdminByAdminCode($adminCode)) {
1206
                return;
1207
            }
1208
1209
            $admin = $pool->getAdminByAdminCode($adminCode);
1210
        } else {
1211
            if (!$pool->hasAdminByClass($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...
1212
                return;
1213
            }
1214
1215
            $admin = $pool->getAdminByClass($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...
1216
        }
1217
1218
        if ($this->hasRequest()) {
1219
            $admin->setRequest($this->getRequest());
1220
        }
1221
1222
        $fieldDescription->setAssociationAdmin($admin);
0 ignored issues
show
Bug introduced by
It seems like $admin defined by $pool->getAdminByClass($...ion->getTargetEntity()) on line 1215 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...
1223
    }
1224
1225
    public function getObject($id): ?object
1226
    {
1227
        $object = $this->getModelManager()->find($this->getClass(), $id);
1228
        foreach ($this->getExtensions() as $extension) {
1229
            $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...
1230
        }
1231
1232
        return $object;
1233
    }
1234
1235
    public function getForm(): ?FormInterface
1236
    {
1237
        $this->buildForm();
1238
1239
        return $this->form;
1240
    }
1241
1242
    public function getList(): ?FieldDescriptionCollection
1243
    {
1244
        $this->buildList();
1245
1246
        return $this->list;
1247
    }
1248
1249
    /**
1250
     * @final since sonata-project/admin-bundle 3.63.0
1251
     */
1252
    public function createQuery($context = 'list'): ProxyQueryInterface
1253
    {
1254
        if (\func_num_args() > 0) {
1255
            @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...
1256
                'The $context argument of '.__METHOD__.' is deprecated since 3.3, to be removed in 4.0.',
1257
                E_USER_DEPRECATED
1258
            );
1259
        }
1260
1261
        $query = $this->getModelManager()->createQuery($this->getClass());
1262
1263
        $query = $this->configureQuery($query);
1264
        foreach ($this->extensions as $extension) {
1265
            $extension->configureQuery($this, $query, $context);
1266
        }
1267
1268
        return $query;
1269
    }
1270
1271
    public function getDatagrid(): ?DatagridInterface
1272
    {
1273
        $this->buildDatagrid();
1274
1275
        return $this->datagrid;
1276
    }
1277
1278
    public function buildTabMenu(string $action, ?AdminInterface $childAdmin = null): ItemInterface
1279
    {
1280
        if ($this->loaded['tab_menu']) {
1281
            return $this->menu;
1282
        }
1283
1284
        $this->loaded['tab_menu'] = true;
1285
1286
        $menu = $this->menuFactory->createItem('root');
1287
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1288
        $menu->setExtra('translation_domain', $this->translationDomain);
1289
1290
        // Prevents BC break with KnpMenuBundle v1.x
1291
        if (method_exists($menu, 'setCurrentUri')) {
1292
            $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...
1293
        }
1294
1295
        $this->configureTabMenu($menu, $action, $childAdmin);
1296
1297
        foreach ($this->getExtensions() as $extension) {
1298
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1299
        }
1300
1301
        $this->menu = $menu;
1302
1303
        return $this->menu;
1304
    }
1305
1306
    public function getSideMenu(string $action, ?AdminInterface $childAdmin = null): ItemInterface
1307
    {
1308
        if ($this->isChild()) {
1309
            return $this->getParent()->getSideMenu($action, $this);
1310
        }
1311
1312
        $this->buildTabMenu($action, $childAdmin);
1313
1314
        return $this->menu;
1315
    }
1316
1317
    public function getRootCode(): string
1318
    {
1319
        return $this->getRoot()->getCode();
1320
    }
1321
1322
    public function getRoot(): AdminInterface
1323
    {
1324
        if (!$this->hasParentFieldDescription()) {
1325
            return $this;
1326
        }
1327
1328
        return $this->getParentFieldDescription()->getAdmin()->getRoot();
1329
    }
1330
1331
    public function setBaseControllerName(string $baseControllerName): void
1332
    {
1333
        $this->baseControllerName = $baseControllerName;
1334
    }
1335
1336
    public function getBaseControllerName(): string
1337
    {
1338
        return $this->baseControllerName;
1339
    }
1340
1341
    public function setLabel(?string $label): void
1342
    {
1343
        $this->label = $label;
1344
    }
1345
1346
    public function getLabel(): ?string
1347
    {
1348
        return $this->label;
1349
    }
1350
1351
    /**
1352
     * @param bool $persist
1353
     *
1354
     * NEXT_MAJOR: remove this method
1355
     *
1356
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
1357
     */
1358
    public function setPersistFilters(bool $persist): void
1359
    {
1360
        @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...
1361
            'The '.__METHOD__.' method is deprecated since version 3.34 and will be removed in 4.0.',
1362
            E_USER_DEPRECATED
1363
        );
1364
1365
        $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...
1366
    }
1367
1368
    public function setFilterPersister(?FilterPersisterInterface $filterPersister = null): void
1369
    {
1370
        $this->filterPersister = $filterPersister;
1371
        // NEXT_MAJOR remove the deprecated property will be removed. Needed for persisted filter condition.
1372
        $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...
1373
    }
1374
1375
    /**
1376
     * NEXT_MAJOR: Remove this method.
1377
     *
1378
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
1379
     */
1380
    public function setMaxPerPage(int $maxPerPage): void
1381
    {
1382
        @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...
1383
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
1384
            __METHOD__
1385
        ), E_USER_DEPRECATED);
1386
1387
        $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...
1388
    }
1389
1390
    public function getMaxPerPage(): int
1391
    {
1392
        // NEXT_MAJOR: Remove this line and uncomment the following.
1393
        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...
1394
        // $sortValues = $this->getModelManager()->getDefaultSortValues($this->class);
1395
1396
        // return $sortValues['_per_page'] ?? 25;
1397
    }
1398
1399
    public function setMaxPageLinks(int $maxPageLinks): void
1400
    {
1401
        $this->maxPageLinks = $maxPageLinks;
1402
    }
1403
1404
    public function getMaxPageLinks(): int
1405
    {
1406
        return $this->maxPageLinks;
1407
    }
1408
1409
    public function getFormGroups(): array
1410
    {
1411
        return $this->formGroups;
1412
    }
1413
1414
    public function setFormGroups(array $formGroups): void
1415
    {
1416
        $this->formGroups = $formGroups;
1417
    }
1418
1419
    public function removeFieldFromFormGroup(string $key): void
1420
    {
1421
        foreach ($this->formGroups as $name => $formGroup) {
1422
            unset($this->formGroups[$name]['fields'][$key]);
1423
1424
            if (empty($this->formGroups[$name]['fields'])) {
1425
                unset($this->formGroups[$name]);
1426
            }
1427
        }
1428
    }
1429
1430
    public function reorderFormGroup(string $group, array $keys): void
1431
    {
1432
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1433
        $formGroups = $this->getFormGroups('sonata_deprecation_mute');
0 ignored issues
show
Unused Code introduced by
The call to AbstractAdmin::getFormGroups() has too many arguments starting with 'sonata_deprecation_mute'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1434
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1435
        $this->setFormGroups($formGroups);
1436
    }
1437
1438
    public function getFormTabs(): array
1439
    {
1440
        return $this->formTabs;
1441
    }
1442
1443
    public function setFormTabs(array $formTabs): void
1444
    {
1445
        $this->formTabs = $formTabs;
1446
    }
1447
1448
    public function getShowTabs(): array
1449
    {
1450
        return $this->showTabs;
1451
    }
1452
1453
    public function setShowTabs(array $showTabs): void
1454
    {
1455
        $this->showTabs = $showTabs;
1456
    }
1457
1458
    public function getShowGroups(): array
1459
    {
1460
        return $this->showGroups;
1461
    }
1462
1463
    public function setShowGroups(array $showGroups): void
1464
    {
1465
        $this->showGroups = $showGroups;
1466
    }
1467
1468
    public function reorderShowGroup(string $group, array $keys): void
1469
    {
1470
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1471
        $showGroups = $this->getShowGroups('sonata_deprecation_mute');
0 ignored issues
show
Unused Code introduced by
The call to AbstractAdmin::getShowGroups() has too many arguments starting with 'sonata_deprecation_mute'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1472
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1473
        $this->setShowGroups($showGroups);
1474
    }
1475
1476
    public function setParentFieldDescription(?FieldDescriptionInterface $parentFieldDescription): void
1477
    {
1478
        $this->parentFieldDescription = $parentFieldDescription;
1479
    }
1480
1481
    public function getParentFieldDescription(): ?FieldDescriptionInterface
1482
    {
1483
        if (!$this->hasParentFieldDescription()) {
1484
            @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...
1485
                'Calling %s() when there is no parent field description is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
1486
                'Use %s::hasParentFieldDescription() to know if there is a parent field description.',
1487
                __METHOD__,
1488
                __CLASS__
1489
            ), E_USER_DEPRECATED);
1490
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1491
            // throw new \LogicException(sprintf(
1492
            //    'Admin "%s" has no parent field description.',
1493
            //    static::class
1494
            // ));
1495
1496
            return null;
1497
        }
1498
1499
        return $this->parentFieldDescription;
1500
    }
1501
1502
    public function hasParentFieldDescription(): bool
1503
    {
1504
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1505
    }
1506
1507
    public function setSubject(?object $subject): void
1508
    {
1509
        if (\is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1510
            $message = <<<'EOT'
1511
You are trying to set entity an instance of "%s",
1512
which is not the one registered with this admin class ("%s").
1513
This is deprecated since 3.5 and will no longer be supported in 4.0.
1514
EOT;
1515
1516
            @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...
1517
                sprintf($message, \get_class($subject), $this->getClass()),
1518
                E_USER_DEPRECATED
1519
            ); // NEXT_MAJOR : throw an exception instead
1520
        }
1521
1522
        $this->subject = $subject;
1523
    }
1524
1525
    public function getSubject(): ?object
1526
    {
1527
        if (!$this->hasSubject()) {
1528
            @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...
1529
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
1530
                'Use %s::hasSubject() to know if there is a subject.',
1531
                __METHOD__,
1532
                __CLASS__
1533
            ), E_USER_DEPRECATED);
1534
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and update the return type
1535
            // throw new \LogicException(sprintf(
1536
            //    'Admin "%s" has no subject.',
1537
            //    static::class
1538
            // ));
1539
1540
            return null;
1541
        }
1542
1543
        return $this->subject;
1544
    }
1545
1546
    public function hasSubject(): bool
1547
    {
1548
        if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
1549
            $id = $this->request->get($this->getIdParameter());
1550
1551
            if (null !== $id) {
1552
                $this->subject = $this->getObject($id);
1553
            }
1554
        }
1555
1556
        return null !== $this->subject;
1557
    }
1558
1559
    public function getFormFieldDescriptions(): array
1560
    {
1561
        $this->buildForm();
1562
1563
        return $this->formFieldDescriptions;
1564
    }
1565
1566
    public function getFormFieldDescription(string $name): ?FieldDescriptionInterface
1567
    {
1568
        $this->buildForm();
1569
1570
        if (!$this->hasFormFieldDescription($name)) {
1571
            @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...
1572
                'Calling %s() when there is no form field description is deprecated since sonata-project/admin-bundle 3.69 and will throw an exception in 4.0. '.
1573
                'Use %s::hasFormFieldDescription() to know if there is a form field description.',
1574
                __METHOD__,
1575
                __CLASS__
1576
            ), E_USER_DEPRECATED);
1577
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1578
            // throw new \LogicException(sprintf(
1579
            //    'Admin "%s" has no form field description for the field %s.',
1580
            //    static::class,
1581
            //    $name
1582
            // ));
1583
1584
            return null;
1585
        }
1586
1587
        return $this->formFieldDescriptions[$name];
1588
    }
1589
1590
    /**
1591
     * Returns true if the admin has a FieldDescription with the given $name.
1592
     */
1593
    public function hasFormFieldDescription(string $name): bool
1594
    {
1595
        $this->buildForm();
1596
1597
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1598
    }
1599
1600
    public function addFormFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1601
    {
1602
        $this->formFieldDescriptions[$name] = $fieldDescription;
1603
    }
1604
1605
    /**
1606
     * remove a FieldDescription.
1607
     */
1608
    public function removeFormFieldDescription(string $name): void
1609
    {
1610
        unset($this->formFieldDescriptions[$name]);
1611
    }
1612
1613
    /**
1614
     * build and return the collection of form FieldDescription.
1615
     *
1616
     * @return FieldDescriptionInterface[] collection of form FieldDescription
1617
     */
1618
    public function getShowFieldDescriptions(): array
1619
    {
1620
        $this->buildShow();
1621
1622
        return $this->showFieldDescriptions;
1623
    }
1624
1625
    /**
1626
     * Returns the form FieldDescription with the given $name.
1627
     */
1628
    public function getShowFieldDescription(string $name): ?FieldDescriptionInterface
1629
    {
1630
        $this->buildShow();
1631
1632
        if (!$this->hasShowFieldDescription($name)) {
1633
            @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...
1634
                'Calling %s() when there is no show field description is deprecated since sonata-project/admin-bundle 3.69 and will throw an exception in 4.0. '.
1635
                'Use %s::hasFormFieldDescription() to know if there is a show field description.',
1636
                __METHOD__,
1637
                __CLASS__
1638
            ), E_USER_DEPRECATED);
1639
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1640
            // throw new \LogicException(sprintf(
1641
            //    'Admin "%s" has no show field description for the field %s.',
1642
            //    static::class,
1643
            //    $name
1644
            // ));
1645
1646
            return null;
1647
        }
1648
1649
        return $this->showFieldDescriptions[$name];
1650
    }
1651
1652
    public function hasShowFieldDescription(string $name): bool
1653
    {
1654
        $this->buildShow();
1655
1656
        return \array_key_exists($name, $this->showFieldDescriptions);
1657
    }
1658
1659
    public function addShowFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1660
    {
1661
        $this->showFieldDescriptions[$name] = $fieldDescription;
1662
    }
1663
1664
    public function removeShowFieldDescription(string $name): void
1665
    {
1666
        unset($this->showFieldDescriptions[$name]);
1667
    }
1668
1669
    public function getListFieldDescriptions(): array
1670
    {
1671
        $this->buildList();
1672
1673
        return $this->listFieldDescriptions;
1674
    }
1675
1676
    public function getListFieldDescription(string $name): ?FieldDescriptionInterface
1677
    {
1678
        $this->buildList();
1679
1680
        if (!$this->hasListFieldDescription($name)) {
1681
            @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...
1682
                'Calling %s() when there is no list field description is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
1683
                'Use %s::hasListFieldDescription(\'%s\') to know if there is a list field description.',
1684
                __METHOD__,
1685
                __CLASS__,
1686
                $name
1687
            ), E_USER_DEPRECATED);
1688
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1689
            // throw new \LogicException(sprintf(
1690
            //    'Admin "%s" has no list field description for %s.',
1691
            //    static::class,
1692
            //    $name
1693
            // ));
1694
1695
            return null;
1696
        }
1697
1698
        return $this->listFieldDescriptions[$name];
1699
    }
1700
1701
    public function hasListFieldDescription(string $name): bool
1702
    {
1703
        $this->buildList();
1704
1705
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1706
    }
1707
1708
    public function addListFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1709
    {
1710
        $this->listFieldDescriptions[$name] = $fieldDescription;
1711
    }
1712
1713
    public function removeListFieldDescription(string $name): void
1714
    {
1715
        unset($this->listFieldDescriptions[$name]);
1716
    }
1717
1718
    public function getFilterFieldDescription(string $name): ?FieldDescriptionInterface
1719
    {
1720
        $this->buildDatagrid();
1721
1722
        if (!$this->hasFilterFieldDescription($name)) {
1723
            @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...
1724
                'Calling %s() when there is no filter field description is deprecated since sonata-project/admin-bundle 3.69 and will throw an exception in 4.0. '.
1725
                'Use %s::hasFilterFieldDescription() to know if there is a filter field description.',
1726
                __METHOD__,
1727
                __CLASS__
1728
            ), E_USER_DEPRECATED);
1729
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1730
            // throw new \LogicException(sprintf(
1731
            //    'Admin "%s" has no filter field description for the field %s.',
1732
            //    static::class,
1733
            //    $name
1734
            // ));
1735
1736
            return null;
1737
        }
1738
1739
        return $this->filterFieldDescriptions[$name];
1740
    }
1741
1742
    public function hasFilterFieldDescription(string $name): bool
1743
    {
1744
        $this->buildDatagrid();
1745
1746
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1747
    }
1748
1749
    public function addFilterFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1750
    {
1751
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1752
    }
1753
1754
    public function removeFilterFieldDescription(string $name): void
1755
    {
1756
        unset($this->filterFieldDescriptions[$name]);
1757
    }
1758
1759
    public function getFilterFieldDescriptions(): array
1760
    {
1761
        $this->buildDatagrid();
1762
1763
        return $this->filterFieldDescriptions;
1764
    }
1765
1766
    public function addChild(AdminInterface $child): void
1767
    {
1768
        $parentAdmin = $this;
1769
        while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
1770
            $parentAdmin = $parentAdmin->getParent();
1771
        }
1772
1773
        if ($parentAdmin->getCode() === $child->getCode()) {
1774
            throw new \RuntimeException(sprintf(
1775
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1776
                $child->getCode(),
1777
                $this->getCode()
1778
            ));
1779
        }
1780
1781
        $this->children[$child->getCode()] = $child;
1782
1783
        $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...
1784
1785
        // NEXT_MAJOR: remove $args and add $field parameter to this function on next Major
1786
1787
        $args = \func_get_args();
1788
1789
        if (isset($args[1])) {
1790
            $child->addParentAssociationMapping($this->getCode(), $args[1]);
1791
        } else {
1792
            @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...
1793
                'Calling "addChild" without second argument is deprecated since'
1794
                .' sonata-project/admin-bundle 3.35 and will not be allowed in 4.0.',
1795
                E_USER_DEPRECATED
1796
            );
1797
        }
1798
    }
1799
1800
    public function hasChild(string $code): bool
1801
    {
1802
        return isset($this->children[$code]);
1803
    }
1804
1805
    public function getChildren(): array
1806
    {
1807
        return $this->children;
1808
    }
1809
1810
    public function getChild(string $code): ?AdminInterface
1811
    {
1812
        if (!$this->hasChild($code)) {
1813
            @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...
1814
                'Calling %s() when there is no child is deprecated since sonata-project/admin-bundle 3.69'
1815
                .' and will throw an exception in 4.0. Use %s::hasChild() to know if the child exists.',
1816
                __METHOD__,
1817
                __CLASS__
1818
            ), E_USER_DEPRECATED);
1819
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
1820
            // throw new \LogicException(sprintf(
1821
            //    'Admin "%s" has no child for the code %s.',
1822
            //    static::class,
1823
            //    $code
1824
            // ));
1825
1826
            return null;
1827
        }
1828
1829
        return $this->children[$code];
1830
    }
1831
1832
    public function setParent(AdminInterface $parent): void
1833
    {
1834
        $this->parent = $parent;
1835
    }
1836
1837
    public function getParent(): ?AdminInterface
1838
    {
1839
        if (!$this->isChild()) {
1840
            @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...
1841
                'Calling %s() when there is no parent is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
1842
                'Use %s::isChild() to know if there is a parent.',
1843
                __METHOD__,
1844
                __CLASS__
1845
            ), E_USER_DEPRECATED);
1846
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
1847
            // throw new \LogicException(sprintf(
1848
            //    'Admin "%s" has no parent.',
1849
            //    static::class
1850
            // ));
1851
1852
            return null;
1853
        }
1854
1855
        return $this->parent;
1856
    }
1857
1858
    final public function getRootAncestor(): AdminInterface
1859
    {
1860
        $parent = $this;
1861
1862
        while ($parent->isChild()) {
1863
            $parent = $parent->getParent();
1864
        }
1865
1866
        return $parent;
1867
    }
1868
1869
    final public function getChildDepth(): int
1870
    {
1871
        $parent = $this;
1872
        $depth = 0;
1873
1874
        while ($parent->isChild()) {
1875
            $parent = $parent->getParent();
1876
            ++$depth;
1877
        }
1878
1879
        return $depth;
1880
    }
1881
1882
    final public function getCurrentLeafChildAdmin(): ?AdminInterface
1883
    {
1884
        $child = $this->getCurrentChildAdmin();
1885
1886
        if (null === $child) {
1887
            return null;
1888
        }
1889
1890
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1891
            $child = $c;
1892
        }
1893
1894
        return $child;
1895
    }
1896
1897
    public function isChild(): bool
1898
    {
1899
        return $this->parent instanceof AdminInterface;
1900
    }
1901
1902
    /**
1903
     * Returns true if the admin has children, false otherwise.
1904
     */
1905
    public function hasChildren(): bool
1906
    {
1907
        return \count($this->children) > 0;
1908
    }
1909
1910
    public function setUniqid(string $uniqid): void
1911
    {
1912
        $this->uniqid = $uniqid;
1913
    }
1914
1915
    public function getUniqid(): string
1916
    {
1917
        if (!$this->uniqid) {
1918
            $this->uniqid = 's'.uniqid();
1919
        }
1920
1921
        return $this->uniqid;
1922
    }
1923
1924
    /**
1925
     * {@inheritdoc}
1926
     */
1927
    public function getClassnameLabel(): string
1928
    {
1929
        return $this->classnameLabel;
1930
    }
1931
1932
    public function getPersistentParameters(): array
1933
    {
1934
        $parameters = [];
1935
1936
        foreach ($this->getExtensions() as $extension) {
1937
            $params = $extension->getPersistentParameters($this);
1938
1939
            $parameters = array_merge($parameters, $params);
1940
        }
1941
1942
        return $parameters;
1943
    }
1944
1945
    /**
1946
     * {@inheritdoc}
1947
     */
1948
    public function getPersistentParameter(string $name)
1949
    {
1950
        $parameters = $this->getPersistentParameters();
1951
1952
        return $parameters[$name] ?? null;
1953
    }
1954
1955
    public function setCurrentChild(bool $currentChild): void
1956
    {
1957
        $this->currentChild = $currentChild;
1958
    }
1959
1960
    /**
1961
     * NEXT_MAJOR: Remove this method.
1962
     *
1963
     * @deprecated since sonata-project/admin-bundle 3.65, to be removed in 4.0
1964
     */
1965
    public function getCurrentChild(): bool
1966
    {
1967
        @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...
1968
            sprintf(
1969
                'The %s() method is deprecated since version 3.65 and will be removed in 4.0. Use %s::isCurrentChild() instead.',
1970
                __METHOD__,
1971
                __CLASS__
1972
            ),
1973
            E_USER_DEPRECATED
1974
        );
1975
1976
        return $this->currentChild;
1977
    }
1978
1979
    public function isCurrentChild(): bool
1980
    {
1981
        return $this->currentChild;
1982
    }
1983
1984
    /**
1985
     * Returns the current child admin instance.
1986
     *
1987
     * @return AdminInterface|null the current child admin instance
1988
     */
1989
    public function getCurrentChildAdmin(): ?AdminInterface
1990
    {
1991
        foreach ($this->children as $children) {
1992
            if ($children->isCurrentChild()) {
1993
                return $children;
1994
            }
1995
        }
1996
1997
        return null;
1998
    }
1999
2000
    public function trans($id, array $parameters = [], $domain = null, $locale = null): string
2001
    {
2002
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2003
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2004
            E_USER_DEPRECATED
2005
        );
2006
2007
        $domain = $domain ?: $this->getTranslationDomain();
2008
2009
        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...
2010
    }
2011
2012
    public function setTranslationDomain(string $translationDomain): void
2013
    {
2014
        $this->translationDomain = $translationDomain;
2015
    }
2016
2017
    public function getTranslationDomain(): string
2018
    {
2019
        return $this->translationDomain;
2020
    }
2021
2022
    /**
2023
     * {@inheritdoc}
2024
     *
2025
     * NEXT_MAJOR: remove this method
2026
     *
2027
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2028
     */
2029
    public function setTranslator(?TranslatorInterface $translator): void
2030
    {
2031
        $args = \func_get_args();
2032
        if (isset($args[1]) && $args[1]) {
2033
            @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...
2034
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2035
                E_USER_DEPRECATED
2036
            );
2037
        }
2038
2039
        $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...
2040
    }
2041
2042
    /**
2043
     * {@inheritdoc}
2044
     *
2045
     * NEXT_MAJOR: remove this method
2046
     *
2047
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2048
     */
2049
    public function getTranslator(): ?TranslatorInterface
2050
    {
2051
        @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...
2052
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2053
            E_USER_DEPRECATED
2054
        );
2055
2056
        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...
2057
    }
2058
2059
    public function getTranslationLabel(string $label, string $context = '', string $type = ''): string
2060
    {
2061
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2062
    }
2063
2064
    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...
2065
    {
2066
        $this->request = $request;
2067
2068
        foreach ($this->getChildren() as $children) {
2069
            $children->setRequest($request);
2070
        }
2071
    }
2072
2073
    public function getRequest(): Request
2074
    {
2075
        if (!$this->request) {
2076
            // NEXT_MAJOR: Throw \LogicException instead.
2077
            throw new \RuntimeException('The Request object has not been set');
2078
        }
2079
2080
        return $this->request;
2081
    }
2082
2083
    public function hasRequest(): bool
2084
    {
2085
        return null !== $this->request;
2086
    }
2087
2088
    public function setFormContractor(?FormContractorInterface $formBuilder): void
2089
    {
2090
        $this->formContractor = $formBuilder;
2091
    }
2092
2093
    public function getFormContractor(): ?FormContractorInterface
2094
    {
2095
        return $this->formContractor;
2096
    }
2097
2098
    public function setDatagridBuilder(?DatagridBuilderInterface $datagridBuilder): void
2099
    {
2100
        $this->datagridBuilder = $datagridBuilder;
2101
    }
2102
2103
    public function getDatagridBuilder(): ?DatagridBuilderInterface
2104
    {
2105
        return $this->datagridBuilder;
2106
    }
2107
2108
    public function setListBuilder(?ListBuilderInterface $listBuilder): void
2109
    {
2110
        $this->listBuilder = $listBuilder;
2111
    }
2112
2113
    public function getListBuilder(): ?ListBuilderInterface
2114
    {
2115
        return $this->listBuilder;
2116
    }
2117
2118
    public function setShowBuilder(?ShowBuilderInterface $showBuilder): void
2119
    {
2120
        $this->showBuilder = $showBuilder;
2121
    }
2122
2123
    public function getShowBuilder(): ?ShowBuilderInterface
2124
    {
2125
        return $this->showBuilder;
2126
    }
2127
2128
    public function setConfigurationPool(?Pool $configurationPool): void
2129
    {
2130
        $this->configurationPool = $configurationPool;
2131
    }
2132
2133
    public function getConfigurationPool(): ?Pool
2134
    {
2135
        return $this->configurationPool;
2136
    }
2137
2138
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator): void
2139
    {
2140
        $this->routeGenerator = $routeGenerator;
2141
    }
2142
2143
    public function getRouteGenerator(): ?RouteGeneratorInterface
2144
    {
2145
        return $this->routeGenerator;
2146
    }
2147
2148
    public function getCode(): string
2149
    {
2150
        return $this->code;
2151
    }
2152
2153
    public function getBaseCodeRoute(): string
2154
    {
2155
        if ($this->isChild()) {
2156
            return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2157
        }
2158
2159
        return $this->getCode();
2160
    }
2161
2162
    public function getModelManager(): ?ModelManagerInterface
2163
    {
2164
        return $this->modelManager;
2165
    }
2166
2167
    public function setModelManager(?ModelManagerInterface $modelManager): void
2168
    {
2169
        $this->modelManager = $modelManager;
2170
    }
2171
2172
    public function getManagerType(): ?string
2173
    {
2174
        return $this->managerType;
2175
    }
2176
2177
    public function setManagerType(?string $type): void
2178
    {
2179
        $this->managerType = $type;
2180
    }
2181
2182
    public function getObjectIdentifier()
2183
    {
2184
        return $this->getCode();
2185
    }
2186
2187
    /**
2188
     * Set the roles and permissions per role.
2189
     */
2190
    public function setSecurityInformation(array $information): void
2191
    {
2192
        $this->securityInformation = $information;
2193
    }
2194
2195
    public function getSecurityInformation(): array
2196
    {
2197
        return $this->securityInformation;
2198
    }
2199
2200
    /**
2201
     * Return the list of permissions the user should have in order to display the admin.
2202
     */
2203
    public function getPermissionsShow(string $context): array
2204
    {
2205
        switch ($context) {
2206
            case self::CONTEXT_DASHBOARD:
2207
            case self::CONTEXT_MENU:
2208
            default:
2209
                return ['LIST'];
2210
        }
2211
    }
2212
2213
    public function showIn(string $context): bool
2214
    {
2215
        switch ($context) {
2216
            case self::CONTEXT_DASHBOARD:
2217
            case self::CONTEXT_MENU:
2218
            default:
2219
                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...
2220
        }
2221
    }
2222
2223
    public function createObjectSecurity(object $object): void
2224
    {
2225
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2226
    }
2227
2228
    public function setSecurityHandler(?SecurityHandlerInterface $securityHandler): void
2229
    {
2230
        $this->securityHandler = $securityHandler;
2231
    }
2232
2233
    public function getSecurityHandler(): ?SecurityHandlerInterface
2234
    {
2235
        return $this->securityHandler;
2236
    }
2237
2238
    /**
2239
     * NEXT_MAJOR: Decide the type declaration for the $name argument, since it is
2240
     * passed as argument 1 for `SecurityHandlerInterface::isGranted()`, which
2241
     * accepts string and array.
2242
     */
2243
    public function isGranted($name, ?object $object = null): bool
2244
    {
2245
        $objectRef = $object ? '/'.spl_object_hash($object).'#'.$this->id($object) : '';
2246
        $key = md5(json_encode($name).$objectRef);
2247
2248
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
2249
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2250
        }
2251
2252
        return $this->cacheIsGranted[$key];
2253
    }
2254
2255
    /**
2256
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2257
     * passed as argument 1 for `ModelManagerInterface::getUrlSafeIdentifier()`, which
2258
     * accepts null.
2259
     */
2260
    public function getUrlSafeIdentifier($model): ?string
2261
    {
2262
        return $this->getModelManager()->getUrlSafeIdentifier($model);
2263
    }
2264
2265
    /**
2266
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2267
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
2268
     * accepts null.
2269
     */
2270
    public function getNormalizedIdentifier($model): ?string
2271
    {
2272
        return $this->getModelManager()->getNormalizedIdentifier($model);
2273
    }
2274
2275
    /**
2276
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2277
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
2278
     * accepts null.
2279
     */
2280
    public function id($model): ?string
2281
    {
2282
        return $this->getNormalizedIdentifier($model);
2283
    }
2284
2285
    public function setValidator(?ValidatorInterface $validator): void
2286
    {
2287
        $this->validator = $validator;
2288
    }
2289
2290
    public function getValidator(): ?ValidatorInterface
2291
    {
2292
        return $this->validator;
2293
    }
2294
2295
    public function getShow(): ?FieldDescriptionCollection
2296
    {
2297
        $this->buildShow();
2298
2299
        return $this->show;
2300
    }
2301
2302
    public function setFormTheme(array $formTheme): void
2303
    {
2304
        $this->formTheme = $formTheme;
2305
    }
2306
2307
    public function getFormTheme(): array
2308
    {
2309
        return $this->formTheme;
2310
    }
2311
2312
    public function setFilterTheme(array $filterTheme): void
2313
    {
2314
        $this->filterTheme = $filterTheme;
2315
    }
2316
2317
    public function getFilterTheme(): array
2318
    {
2319
        return $this->filterTheme;
2320
    }
2321
2322
    public function addExtension(AdminExtensionInterface $extension): void
2323
    {
2324
        $this->extensions[] = $extension;
2325
    }
2326
2327
    public function getExtensions(): array
2328
    {
2329
        return $this->extensions;
2330
    }
2331
2332
    public function setMenuFactory(?FactoryInterface $menuFactory): void
2333
    {
2334
        $this->menuFactory = $menuFactory;
2335
    }
2336
2337
    public function getMenuFactory(): ?FactoryInterface
2338
    {
2339
        return $this->menuFactory;
2340
    }
2341
2342
    public function setRouteBuilder(?RouteBuilderInterface $routeBuilder): void
2343
    {
2344
        $this->routeBuilder = $routeBuilder;
2345
    }
2346
2347
    public function getRouteBuilder(): ?RouteBuilderInterface
2348
    {
2349
        return $this->routeBuilder;
2350
    }
2351
2352
    /**
2353
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since there
2354
     * are tests ensuring to accept null (`GetShortObjectDescriptionActionTest::testGetShortObjectDescriptionActionEmptyObjectIdAsJson()`).
2355
     */
2356
    public function toString($object): string
2357
    {
2358
        if (!\is_object($object)) {
2359
            return '';
2360
        }
2361
2362
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2363
            return (string) $object;
2364
        }
2365
2366
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2367
    }
2368
2369
    public function setLabelTranslatorStrategy(?LabelTranslatorStrategyInterface $labelTranslatorStrategy): void
2370
    {
2371
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2372
    }
2373
2374
    public function getLabelTranslatorStrategy(): ?LabelTranslatorStrategyInterface
2375
    {
2376
        return $this->labelTranslatorStrategy;
2377
    }
2378
2379
    public function supportsPreviewMode(): bool
2380
    {
2381
        return $this->supportsPreviewMode;
2382
    }
2383
2384
    /**
2385
     * NEXT_MAJOR: Remove this.
2386
     *
2387
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
2388
     *
2389
     * Set custom per page options.
2390
     */
2391
    public function setPerPageOptions(array $options): void
2392
    {
2393
        @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...
2394
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
2395
            __METHOD__
2396
        ), E_USER_DEPRECATED);
2397
2398
        $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...
2399
    }
2400
2401
    /**
2402
     * Returns predefined per page options.
2403
     */
2404
    public function getPerPageOptions(): array
2405
    {
2406
        // NEXT_MAJOR: Remove this line and uncomment the following
2407
        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...
2408
//        $perPageOptions = $this->getModelManager()->getDefaultPerPageOptions($this->class);
2409
//        $perPageOptions[] = $this->getMaxPerPage();
2410
//
2411
//        $perPageOptions = array_unique($perPageOptions);
2412
//        sort($perPageOptions);
2413
//
2414
//        return $perPageOptions;
2415
    }
2416
2417
    /**
2418
     * Set pager type.
2419
     */
2420
    public function setPagerType(string $pagerType): void
2421
    {
2422
        $this->pagerType = $pagerType;
2423
    }
2424
2425
    /**
2426
     * Get pager type.
2427
     */
2428
    public function getPagerType(): string
2429
    {
2430
        return $this->pagerType;
2431
    }
2432
2433
    /**
2434
     * Returns true if the per page value is allowed, false otherwise.
2435
     */
2436
    public function determinedPerPageValue(int $perPage): bool
2437
    {
2438
        return \in_array($perPage, $this->getPerPageOptions(), true);
2439
    }
2440
2441
    public function isAclEnabled(): bool
2442
    {
2443
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2444
    }
2445
2446
    /**
2447
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since it is
2448
     * passed as argument 1 to `toString()` method, which currently accepts null.
2449
     */
2450
    public function getObjectMetadata($object): MetadataInterface
2451
    {
2452
        return new Metadata($this->toString($object));
2453
    }
2454
2455
    public function getListModes(): array
2456
    {
2457
        return $this->listModes;
2458
    }
2459
2460
    public function setListMode(string $mode): void
2461
    {
2462
        if (!$this->hasRequest()) {
2463
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2464
        }
2465
2466
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2467
    }
2468
2469
    public function getListMode(): string
2470
    {
2471
        if (!$this->hasRequest()) {
2472
            return 'list';
2473
        }
2474
2475
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2476
    }
2477
2478
    public function getAccessMapping(): array
2479
    {
2480
        return $this->accessMapping;
2481
    }
2482
2483
    public function checkAccess(string $action, ?object $object = null): void
2484
    {
2485
        $access = $this->getAccess();
2486
2487
        if (!\array_key_exists($action, $access)) {
2488
            throw new \InvalidArgumentException(sprintf(
2489
                'Action "%s" could not be found in access mapping.'
2490
                .' Please make sure your action is defined into your admin class accessMapping property.',
2491
                $action
2492
            ));
2493
        }
2494
2495
        if (!\is_array($access[$action])) {
2496
            $access[$action] = [$access[$action]];
2497
        }
2498
2499
        foreach ($access[$action] as $role) {
2500
            if (false === $this->isGranted($role, $object)) {
2501
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2502
            }
2503
        }
2504
    }
2505
2506
    /**
2507
     * {@inheritdoc}
2508
     */
2509
    public function hasAccess(string $action, ?object $object = null): bool
2510
    {
2511
        $access = $this->getAccess();
2512
2513
        if (!\array_key_exists($action, $access)) {
2514
            return false;
2515
        }
2516
2517
        if (!\is_array($access[$action])) {
2518
            $access[$action] = [$access[$action]];
2519
        }
2520
2521
        foreach ($access[$action] as $role) {
2522
            if (false === $this->isGranted($role, $object)) {
2523
                return false;
2524
            }
2525
        }
2526
2527
        return true;
2528
    }
2529
2530
    final public function getActionButtons(string $action, ?object $object = null): array
2531
    {
2532
        $buttonList = [];
2533
2534
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
2535
            && $this->hasAccess('create')
2536
            && $this->hasRoute('create')
2537
        ) {
2538
            $buttonList['create'] = [
2539
                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2540
            ];
2541
        }
2542
2543
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
2544
            && $this->canAccessObject('edit', $object)
2545
            && $this->hasRoute('edit')
2546
        ) {
2547
            $buttonList['edit'] = [
2548
                'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2549
            ];
2550
        }
2551
2552
        if (\in_array($action, ['show', 'edit', 'acl'], true)
2553
            && $this->canAccessObject('history', $object)
2554
            && $this->hasRoute('history')
2555
        ) {
2556
            $buttonList['history'] = [
2557
                'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2558
            ];
2559
        }
2560
2561
        if (\in_array($action, ['edit', 'history'], true)
2562
            && $this->isAclEnabled()
2563
            && $this->canAccessObject('acl', $object)
2564
            && $this->hasRoute('acl')
2565
        ) {
2566
            $buttonList['acl'] = [
2567
                'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2568
            ];
2569
        }
2570
2571
        if (\in_array($action, ['edit', 'history', 'acl'], true)
2572
            && $this->canAccessObject('show', $object)
2573
            && \count($this->getShow()) > 0
2574
            && $this->hasRoute('show')
2575
        ) {
2576
            $buttonList['show'] = [
2577
                'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2578
            ];
2579
        }
2580
2581
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
2582
            && $this->hasAccess('list')
2583
            && $this->hasRoute('list')
2584
        ) {
2585
            $buttonList['list'] = [
2586
                'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2587
            ];
2588
        }
2589
2590
        $buttonList = $this->configureActionButtons($buttonList, $action, $object);
2591
2592
        foreach ($this->getExtensions() as $extension) {
2593
            $buttonList = $extension->configureActionButtons($this, $buttonList, $action, $object);
2594
        }
2595
2596
        return $buttonList;
2597
    }
2598
2599
    /**
2600
     * {@inheritdoc}
2601
     */
2602
    public function getDashboardActions(): array
2603
    {
2604
        $actions = [];
2605
2606
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2607
            $actions['create'] = [
2608
                'label' => 'link_add',
2609
                'translation_domain' => 'SonataAdminBundle',
2610
                'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2611
                'url' => $this->generateUrl('create'),
2612
                'icon' => 'plus-circle',
2613
            ];
2614
        }
2615
2616
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2617
            $actions['list'] = [
2618
                'label' => 'link_list',
2619
                'translation_domain' => 'SonataAdminBundle',
2620
                'url' => $this->generateUrl('list'),
2621
                'icon' => 'list',
2622
            ];
2623
        }
2624
2625
        return $actions;
2626
    }
2627
2628
    /**
2629
     * {@inheritdoc}
2630
     */
2631
    final public function showMosaicButton($isShown): void
2632
    {
2633
        if ($isShown) {
2634
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
2635
        } else {
2636
            unset($this->listModes['mosaic']);
2637
        }
2638
    }
2639
2640
    final public function getSearchResultLink(object $object): ?string
2641
    {
2642
        foreach ($this->searchResultActions as $action) {
2643
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2644
                return $this->generateObjectUrl($action, $object);
2645
            }
2646
        }
2647
2648
        return null;
2649
    }
2650
2651
    /**
2652
     * Checks if a filter type is set to a default value.
2653
     */
2654
    final public function isDefaultFilter(string $name): bool
2655
    {
2656
        $filter = $this->getFilterParameters();
2657
        $default = $this->getDefaultFilterValues();
2658
2659
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
2660
            return false;
2661
        }
2662
2663
        return $filter[$name] === $default[$name];
2664
    }
2665
2666
    public function canAccessObject(string $action, ?object $object = null): bool
2667
    {
2668
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2669
    }
2670
2671
    public function configureActionButtons(array $buttonList, string $action, ?object $object = null): array
2672
    {
2673
        return $buttonList;
2674
    }
2675
2676
    /**
2677
     * Hook to run after initialization.
2678
     */
2679
    protected function configure(): void
2680
    {
2681
    }
2682
2683
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
2684
    {
2685
        return $query;
2686
    }
2687
2688
    /**
2689
     * urlize the given word.
2690
     *
2691
     * @param string $sep the separator
2692
     */
2693
    final protected function urlize(string $word, string $sep = '_'): string
2694
    {
2695
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
2696
    }
2697
2698
    final protected function getTemplateRegistry(): MutableTemplateRegistryInterface
2699
    {
2700
        return $this->templateRegistry;
2701
    }
2702
2703
    /**
2704
     * Returns a list of default sort values.
2705
     *
2706
     * @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...
2707
     */
2708
    final protected function getDefaultSortValues(): array
2709
    {
2710
        $defaultSortValues = [];
2711
2712
        $this->configureDefaultSortValues($defaultSortValues);
2713
2714
        foreach ($this->getExtensions() as $extension) {
2715
            // NEXT_MAJOR: remove method check
2716
            if (method_exists($extension, 'configureDefaultSortValues')) {
2717
                $extension->configureDefaultSortValues($this, $defaultSortValues);
2718
            }
2719
        }
2720
2721
        return $defaultSortValues;
2722
    }
2723
2724
    /**
2725
     * Returns a list of default filters.
2726
     */
2727
    final protected function getDefaultFilterValues(): array
2728
    {
2729
        $defaultFilterValues = [];
2730
2731
        $this->configureDefaultFilterValues($defaultFilterValues);
2732
2733
        foreach ($this->getExtensions() as $extension) {
2734
            // NEXT_MAJOR: remove method check
2735
            if (method_exists($extension, 'configureDefaultFilterValues')) {
2736
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2737
            }
2738
        }
2739
2740
        return $defaultFilterValues;
2741
    }
2742
2743
    protected function configureFormFields(FormMapper $form): void
2744
    {
2745
    }
2746
2747
    protected function configureListFields(ListMapper $list): void
2748
    {
2749
    }
2750
2751
    protected function configureDatagridFilters(DatagridMapper $filter): void
2752
    {
2753
    }
2754
2755
    protected function configureShowFields(ShowMapper $show): void
2756
    {
2757
    }
2758
2759
    protected function configureRoutes(RouteCollection $collection): void
2760
    {
2761
    }
2762
2763
    /**
2764
     * Allows you to customize batch actions.
2765
     *
2766
     * @param array $actions List of actions
2767
     */
2768
    protected function configureBatchActions(array $actions): array
2769
    {
2770
        return $actions;
2771
    }
2772
2773
    /**
2774
     * NEXT_MAJOR: remove this method.
2775
     *
2776
     * @deprecated Use configureTabMenu instead
2777
     */
2778
    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...
2779
    {
2780
    }
2781
2782
    /**
2783
     * Configures the tab menu in your admin.
2784
     */
2785
    protected function configureTabMenu(ItemInterface $menu, string $action, ?AdminInterface $childAdmin = null): void
2786
    {
2787
        // Use configureSideMenu not to mess with previous overrides
2788
        // NEXT_MAJOR: remove this line
2789
        $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...
2790
    }
2791
2792
    /**
2793
     * build the view FieldDescription array.
2794
     */
2795
    protected function buildShow(): void
2796
    {
2797
        if ($this->loaded['show']) {
2798
            return;
2799
        }
2800
2801
        $this->loaded['show'] = true;
2802
2803
        $this->show = $this->getShowBuilder()->getBaseList();
2804
        $mapper = new ShowMapper($this->getShowBuilder(), $this->show, $this);
2805
2806
        $this->configureShowFields($mapper);
2807
2808
        foreach ($this->getExtensions() as $extension) {
2809
            $extension->configureShowFields($mapper);
2810
        }
2811
    }
2812
2813
    /**
2814
     * build the list FieldDescription array.
2815
     */
2816
    protected function buildList(): void
2817
    {
2818
        if ($this->loaded['list']) {
2819
            return;
2820
        }
2821
2822
        $this->loaded['list'] = true;
2823
2824
        $this->list = $this->getListBuilder()->getBaseList();
2825
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
2826
2827
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
2828
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2829
                $this->getClass(),
2830
                'batch',
2831
                [
2832
                    'label' => 'batch',
2833
                    'code' => '_batch',
2834
                    'sortable' => false,
2835
                    'virtual_field' => true,
2836
                ]
2837
            );
2838
2839
            $fieldDescription->setAdmin($this);
2840
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
2841
2842
            $mapper->add($fieldDescription, ListMapper::TYPE_BATCH);
2843
        }
2844
2845
        $this->configureListFields($mapper);
2846
2847
        foreach ($this->getExtensions() as $extension) {
2848
            $extension->configureListFields($mapper);
2849
        }
2850
2851
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
2852
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2853
                $this->getClass(),
2854
                'select',
2855
                [
2856
                    'label' => false,
2857
                    'code' => '_select',
2858
                    'sortable' => false,
2859
                    'virtual_field' => false,
2860
                ]
2861
            );
2862
2863
            $fieldDescription->setAdmin($this);
2864
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
2865
2866
            $mapper->add($fieldDescription, ListMapper::TYPE_SELECT);
2867
        }
2868
    }
2869
2870
    /**
2871
     * Build the form FieldDescription collection.
2872
     */
2873
    protected function buildForm(): void
2874
    {
2875
        if ($this->loaded['form']) {
2876
            return;
2877
        }
2878
2879
        $this->loaded['form'] = true;
2880
2881
        // append parent object if any
2882
        // todo : clean the way the Admin class can retrieve set the object
2883
        if ($this->isChild() && $this->getParentAssociationMapping()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getParentAssociationMapping() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2884
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
2885
2886
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
2887
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
2888
2889
            $object = $this->getSubject();
2890
2891
            $value = $propertyAccessor->getValue($object, $propertyPath);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 2889 can also be of type null; however, Symfony\Component\Proper...orInterface::getValue() does only seem to accept object|array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
2892
2893
            if (\is_array($value) || $value instanceof \ArrayAccess) {
2894
                $value[] = $parent;
2895
                $propertyAccessor->setValue($object, $propertyPath, $value);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 2889 can also be of type null; however, Symfony\Component\Proper...orInterface::setValue() does only seem to accept object|array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
2896
            } else {
2897
                $propertyAccessor->setValue($object, $propertyPath, $parent);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 2889 can also be of type null; however, Symfony\Component\Proper...orInterface::setValue() does only seem to accept object|array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
2898
            }
2899
        }
2900
2901
        $formBuilder = $this->getFormBuilder();
2902
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
2903
            $this->preValidate($event->getData());
2904
        }, 100);
2905
2906
        $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...
2907
    }
2908
2909
    /**
2910
     * Gets the subclass corresponding to the given name.
2911
     *
2912
     * @param string $name The name of the sub class
2913
     *
2914
     * @return string the subclass
2915
     */
2916
    protected function getSubClass(string $name): string
2917
    {
2918
        if ($this->hasSubClass($name)) {
2919
            return $this->subClasses[$name];
2920
        }
2921
2922
        // NEXT_MAJOR: Throw \LogicException instead.
2923
        throw new \RuntimeException(sprintf(
2924
            'Unable to find the subclass `%s` for admin `%s`',
2925
            $name,
2926
            static::class
2927
        ));
2928
    }
2929
2930
    /**
2931
     * Attach the inline validator to the model metadata, this must be done once per admin.
2932
     */
2933
    protected function attachInlineValidator(): void
2934
    {
2935
        $admin = $this;
2936
2937
        // add the custom inline validation option
2938
        $metadata = $this->validator->getMetadataFor($this->getClass());
2939
2940
        $metadata->addConstraint(new InlineConstraint([
2941
            'service' => $this,
2942
            'method' => static function (ErrorElement $errorElement, $object) use ($admin): void {
2943
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
2944
2945
                // This avoid the main validation to be cascaded to children
2946
                // The problem occurs when a model Page has a collection of Page as property
2947
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
2948
                    return;
2949
                }
2950
2951
                $admin->validate($errorElement, $object);
2952
2953
                foreach ($admin->getExtensions() as $extension) {
2954
                    $extension->validate($admin, $errorElement, $object);
2955
                }
2956
            },
2957
            'serializingWarning' => true,
2958
        ]));
2959
    }
2960
2961
    /**
2962
     * NEXT_MAJOR: Remove this function.
2963
     *
2964
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
2965
     *
2966
     * Predefine per page options.
2967
     */
2968
    protected function predefinePerPageOptions(): void
2969
    {
2970
        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...
2971
        $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...
2972
        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...
2973
    }
2974
2975
    /**
2976
     * Return list routes with permissions name.
2977
     *
2978
     * @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...
2979
     */
2980
    protected function getAccess(): array
2981
    {
2982
        $access = array_merge([
2983
            'acl' => 'MASTER',
2984
            'export' => 'EXPORT',
2985
            'historyCompareRevisions' => 'EDIT',
2986
            'historyViewRevision' => 'EDIT',
2987
            'history' => 'EDIT',
2988
            'edit' => 'EDIT',
2989
            'show' => 'VIEW',
2990
            'create' => 'CREATE',
2991
            'delete' => 'DELETE',
2992
            'batchDelete' => 'DELETE',
2993
            'list' => 'LIST',
2994
        ], $this->getAccessMapping());
2995
2996
        foreach ($this->extensions as $extension) {
2997
            $access = array_merge($access, $extension->getAccessMapping($this));
2998
        }
2999
3000
        return $access;
3001
    }
3002
3003
    /**
3004
     * Configures a list of default filters.
3005
     */
3006
    protected function configureDefaultFilterValues(array &$filterValues): void
3007
    {
3008
    }
3009
3010
    /**
3011
     * Configures a list of default sort values.
3012
     *
3013
     * Example:
3014
     *   $sortValues['_sort_by'] = 'foo'
3015
     *   $sortValues['_sort_order'] = 'DESC'
3016
     */
3017
    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...
3018
    {
3019
    }
3020
3021
    /**
3022
     * {@inheritdoc}
3023
     */
3024
    private function buildDatagrid(): void
3025
    {
3026
        if ($this->loaded['datagrid']) {
3027
            return;
3028
        }
3029
3030
        $this->loaded['datagrid'] = true;
3031
3032
        $filterParameters = $this->getFilterParameters();
3033
3034
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
3035
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
3036
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
3037
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
3038
            } else {
3039
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
3040
                    $this->getClass(),
3041
                    $filterParameters['_sort_by'],
3042
                    []
3043
                );
3044
3045
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
3046
            }
3047
        }
3048
3049
        // initialize the datagrid
3050
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
3051
3052
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
3053
3054
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
3055
3056
        // build the datagrid filter
3057
        $this->configureDatagridFilters($mapper);
3058
3059
        // ok, try to limit to add parent filter
3060
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getParentAssociationMapping() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
3061
            $mapper->add($this->getParentAssociationMapping(), null, [
3062
                'show_filter' => false,
3063
                'label' => false,
3064
                'field_type' => ModelHiddenType::class,
3065
                'field_options' => [
3066
                    'model_manager' => $this->getModelManager(),
3067
                ],
3068
                'operator_type' => HiddenType::class,
3069
            ], null, null, [
3070
                'admin_code' => $this->getParent()->getCode(),
3071
            ]);
3072
        }
3073
3074
        foreach ($this->getExtensions() as $extension) {
3075
            $extension->configureDatagridFilters($mapper);
3076
        }
3077
    }
3078
3079
    /**
3080
     * Build all the related urls to the current admin.
3081
     */
3082
    private function buildRoutes(): void
3083
    {
3084
        if ($this->loaded['routes']) {
3085
            return;
3086
        }
3087
3088
        $this->loaded['routes'] = true;
3089
3090
        $this->routes = new RouteCollection(
3091
            $this->getBaseCodeRoute(),
3092
            $this->getBaseRouteName(),
3093
            $this->getBaseRoutePattern(),
3094
            $this->getBaseControllerName()
3095
        );
3096
3097
        $this->routeBuilder->build($this, $this->routes);
3098
3099
        $this->configureRoutes($this->routes);
3100
3101
        foreach ($this->getExtensions() as $extension) {
3102
            $extension->configureRoutes($this, $this->routes);
3103
        }
3104
    }
3105
}
3106
3107
class_exists(ErrorElement::class);
3108