Completed
Push — master ( be024c...a4094f )
by Vincent
16s queued 11s
created

AbstractAdmin::isCurrentChild()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

Loading history...
901
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', static::class));
902
            }
903
904
            $this->cachedBaseRouteName = sprintf(
905
                'admin_%s%s_%s',
906
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
907
                $this->urlize($matches[3]),
908
                $this->urlize($matches[5])
909
            );
910
        }
911
912
        return $this->cachedBaseRouteName;
913
    }
914
915
    public function getClass()
916
    {
917
        if ($this->hasActiveSubClass()) {
918
            if ($this->getParentFieldDescription()) {
919
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
920
            }
921
922
            $subClass = $this->getRequest()->query->get('subclass');
923
924
            if (!$this->hasSubClass($subClass)) {
925
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
926
            }
927
928
            return $this->getSubClass($subClass);
929
        }
930
931
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
932
        if ($this->subject && \is_object($this->subject)) {
933
            return ClassUtils::getClass($this->subject);
934
        }
935
936
        return $this->class;
937
    }
938
939
    public function getSubClasses(): array
940
    {
941
        return $this->subClasses;
942
    }
943
944
    /**
945
     * NEXT_MAJOR: remove this method.
946
     */
947
    public function addSubClass($subClass): void
948
    {
949
        @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...
950
            'Method "%s" is deprecated since sonata-project/admin-bundle 3.30 and will be removed in 4.0.',
951
            __METHOD__
952
        ), E_USER_DEPRECATED);
953
954
        if (!\in_array($subClass, $this->subClasses, true)) {
955
            $this->subClasses[] = $subClass;
956
        }
957
    }
958
959
    public function setSubClasses(array $subClasses): void
960
    {
961
        $this->subClasses = $subClasses;
962
    }
963
964
    public function hasSubClass($name)
965
    {
966
        return isset($this->subClasses[$name]);
967
    }
968
969
    public function hasActiveSubClass()
970
    {
971
        if (\count($this->subClasses) > 0 && $this->request) {
972
            return null !== $this->getRequest()->query->get('subclass');
973
        }
974
975
        return false;
976
    }
977
978
    public function getActiveSubClass()
979
    {
980
        if (!$this->hasActiveSubClass()) {
981
            @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...
982
                'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52 and will throw an exception in 4.0. '.
983
                'Use %s::hasActiveSubClass() to know if there is an active subclass.',
984
                __METHOD__,
985
                __CLASS__
986
            ), E_USER_DEPRECATED);
987
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
988
            // throw new \LogicException(sprintf(
989
            //    'Admin "%s" has no active subclass.',
990
            //    static::class
991
            // ));
992
993
            return null;
994
        }
995
996
        return $this->getSubClass($this->getActiveSubclassCode());
997
    }
998
999
    public function getActiveSubclassCode()
1000
    {
1001
        if (!$this->hasActiveSubClass()) {
1002
            @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...
1003
                'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52 and will throw an exception in 4.0. '.
1004
                'Use %s::hasActiveSubClass() to know if there is an active subclass.',
1005
                __METHOD__,
1006
                __CLASS__
1007
            ), E_USER_DEPRECATED);
1008
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1009
            // throw new \LogicException(sprintf(
1010
            //    'Admin "%s" has no active subclass.',
1011
            //    static::class
1012
            // ));
1013
1014
            return null;
1015
        }
1016
1017
        $subClass = $this->getRequest()->query->get('subclass');
1018
1019
        if (!$this->hasSubClass($subClass)) {
1020
            @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...
1021
                'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52 and will throw an exception in 4.0. '.
1022
                'Use %s::hasActiveSubClass() to know if there is an active subclass.',
1023
                __METHOD__,
1024
                __CLASS__
1025
            ), E_USER_DEPRECATED);
1026
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1027
            // throw new \LogicException(sprintf(
1028
            //    'Admin "%s" has no active subclass.',
1029
            //    static::class
1030
            // ));
1031
1032
            return null;
1033
        }
1034
1035
        return $subClass;
1036
    }
1037
1038
    public function getBatchActions()
1039
    {
1040
        $actions = [];
1041
1042
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1043
            $actions['delete'] = [
1044
                'label' => 'action_delete',
1045
                'translation_domain' => 'SonataAdminBundle',
1046
                'ask_confirmation' => true, // by default always true
1047
            ];
1048
        }
1049
1050
        $actions = $this->configureBatchActions($actions);
1051
1052
        foreach ($this->getExtensions() as $extension) {
1053
            $actions = $extension->configureBatchActions($this, $actions);
1054
        }
1055
1056
        foreach ($actions  as $name => &$action) {
1057
            if (!\array_key_exists('label', $action)) {
1058
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1059
            }
1060
1061
            if (!\array_key_exists('translation_domain', $action)) {
1062
                $action['translation_domain'] = $this->getTranslationDomain();
1063
            }
1064
        }
1065
1066
        return $actions;
1067
    }
1068
1069
    public function getRoutes()
1070
    {
1071
        $this->buildRoutes();
1072
1073
        return $this->routes;
1074
    }
1075
1076
    public function getRouterIdParameter()
1077
    {
1078
        return '{'.$this->getIdParameter().'}';
1079
    }
1080
1081
    public function getIdParameter()
1082
    {
1083
        $parameter = 'id';
1084
1085
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1086
            $parameter = 'child'.ucfirst($parameter);
1087
        }
1088
1089
        return $parameter;
1090
    }
1091
1092
    public function hasRoute($name)
1093
    {
1094
        if (!$this->routeGenerator) {
1095
            throw new \RuntimeException('RouteGenerator cannot be null');
1096
        }
1097
1098
        return $this->routeGenerator->hasAdminRoute($this, $name);
1099
    }
1100
1101
    public function isCurrentRoute(string $name, ?string $adminCode = null): bool
1102
    {
1103
        if (!$this->hasRequest()) {
1104
            return false;
1105
        }
1106
1107
        $request = $this->getRequest();
1108
        $route = $request->get('_route');
1109
1110
        if ($adminCode) {
1111
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1112
        } else {
1113
            $admin = $this;
1114
        }
1115
1116
        if (!$admin) {
1117
            return false;
1118
        }
1119
1120
        return ($admin->getBaseRouteName().'_'.$name) === $route;
1121
    }
1122
1123
    public function generateObjectUrl($name, $object, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1124
    {
1125
        $parameters['id'] = $this->getUrlsafeIdentifier($object);
1126
1127
        return $this->generateUrl($name, $parameters, $absolute);
1128
    }
1129
1130
    public function generateUrl($name, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1131
    {
1132
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $absolute);
0 ignored issues
show
Documentation introduced by
$absolute is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1133
    }
1134
1135
    public function generateMenuUrl($name, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1136
    {
1137
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $absolute);
0 ignored issues
show
Documentation introduced by
$absolute is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1138
    }
1139
1140
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry): void
1141
    {
1142
        $this->templateRegistry = $templateRegistry;
1143
    }
1144
1145
    /**
1146
     * @param array<string, string> $templates
1147
     */
1148
    public function setTemplates(array $templates): void
1149
    {
1150
        $this->getTemplateRegistry()->setTemplates($templates);
1151
    }
1152
1153
    /**
1154
     * {@inheritdoc}
1155
     */
1156
    public function setTemplate($name, $template): void
1157
    {
1158
        $this->getTemplateRegistry()->setTemplate($name, $template);
1159
    }
1160
1161
    public function getNewInstance()
1162
    {
1163
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1164
        foreach ($this->getExtensions() as $extension) {
1165
            $extension->alterNewInstance($this, $object);
1166
        }
1167
1168
        return $object;
1169
    }
1170
1171
    public function getFormBuilder()
1172
    {
1173
        $this->formOptions['data_class'] = $this->getClass();
1174
1175
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1176
            $this->getUniqid(),
1177
            $this->formOptions
1178
        );
1179
1180
        $this->defineFormBuilder($formBuilder);
1181
1182
        return $formBuilder;
1183
    }
1184
1185
    /**
1186
     * This method is being called by the main admin class and the child class,
1187
     * the getFormBuilder is only call by the main admin class.
1188
     */
1189
    public function defineFormBuilder(FormBuilderInterface $formBuilder): void
1190
    {
1191
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1192
1193
        $this->configureFormFields($mapper);
1194
1195
        foreach ($this->getExtensions() as $extension) {
1196
            $extension->configureFormFields($mapper);
1197
        }
1198
1199
        $this->attachInlineValidator();
1200
    }
1201
1202
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription): void
1203
    {
1204
        $pool = $this->getConfigurationPool();
1205
1206
        $adminCode = $fieldDescription->getOption('admin_code');
1207
1208
        if (null !== $adminCode) {
1209
            $admin = $pool->getAdminByAdminCode($adminCode);
1210
        } else {
1211
            $admin = $pool->getAdminByClass($fieldDescription->getTargetEntity());
1212
        }
1213
1214
        if (!$admin) {
1215
            return;
1216
        }
1217
1218
        if ($this->hasRequest()) {
1219
            $admin->setRequest($this->getRequest());
1220
        }
1221
1222
        $fieldDescription->setAssociationAdmin($admin);
1223
    }
1224
1225
    public function getObject($id)
1226
    {
1227
        $object = $this->getModelManager()->find($this->getClass(), $id);
1228
        foreach ($this->getExtensions() as $extension) {
1229
            $extension->alterObject($this, $object);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getModelManager()...$this->getClass(), $id) on line 1227 can also be of type null; however, Sonata\AdminBundle\Admin...nterface::alterObject() does only seem to accept object, 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...
1230
        }
1231
1232
        return $object;
1233
    }
1234
1235
    public function getForm()
1236
    {
1237
        $this->buildForm();
1238
1239
        return $this->form;
1240
    }
1241
1242
    public function getList()
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')
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()
1272
    {
1273
        $this->buildDatagrid();
1274
1275
        return $this->datagrid;
1276
    }
1277
1278
    public function buildTabMenu($action, AdminInterface $childAdmin = null): MenuItemInterface
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
    /**
1307
     * @param string $action
1308
     *
1309
     * @return ItemInterface
1310
     */
1311
    public function getSideMenu($action, AdminInterface $childAdmin = null)
1312
    {
1313
        if ($this->isChild()) {
1314
            return $this->getParent()->getSideMenu($action, $this);
1315
        }
1316
1317
        $this->buildTabMenu($action, $childAdmin);
1318
1319
        return $this->menu;
1320
    }
1321
1322
    public function getRootCode(): string
1323
    {
1324
        return $this->getRoot()->getCode();
1325
    }
1326
1327
    public function getRoot(): AdminInterface
1328
    {
1329
        $parentFieldDescription = $this->getParentFieldDescription();
1330
1331
        if (!$parentFieldDescription) {
1332
            return $this;
1333
        }
1334
1335
        return $parentFieldDescription->getAdmin()->getRoot();
1336
    }
1337
1338
    public function setBaseControllerName($baseControllerName): void
1339
    {
1340
        $this->baseControllerName = $baseControllerName;
1341
    }
1342
1343
    public function getBaseControllerName()
1344
    {
1345
        return $this->baseControllerName;
1346
    }
1347
1348
    /**
1349
     * @param string $label
1350
     */
1351
    public function setLabel($label): void
1352
    {
1353
        $this->label = $label;
1354
    }
1355
1356
    public function getLabel()
1357
    {
1358
        return $this->label;
1359
    }
1360
1361
    /**
1362
     * @param bool $persist
1363
     *
1364
     * NEXT_MAJOR: remove this method
1365
     *
1366
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
1367
     */
1368
    public function setPersistFilters($persist): void
1369
    {
1370
        @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...
1371
            'The '.__METHOD__.' method is deprecated since version 3.34 and will be removed in 4.0.',
1372
            E_USER_DEPRECATED
1373
        );
1374
1375
        $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...
1376
    }
1377
1378
    public function setFilterPersister(FilterPersisterInterface $filterPersister = null): void
1379
    {
1380
        $this->filterPersister = $filterPersister;
1381
        // NEXT_MAJOR remove the deprecated property will be removed. Needed for persisted filter condition.
1382
        $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...
1383
    }
1384
1385
    /**
1386
     * @param int $maxPerPage
1387
     */
1388
    public function setMaxPerPage($maxPerPage): void
1389
    {
1390
        $this->maxPerPage = $maxPerPage;
1391
    }
1392
1393
    /**
1394
     * @return int
1395
     */
1396
    public function getMaxPerPage()
1397
    {
1398
        return $this->maxPerPage;
1399
    }
1400
1401
    /**
1402
     * @param int $maxPageLinks
1403
     */
1404
    public function setMaxPageLinks($maxPageLinks): void
1405
    {
1406
        $this->maxPageLinks = $maxPageLinks;
1407
    }
1408
1409
    /**
1410
     * @return int
1411
     */
1412
    public function getMaxPageLinks()
1413
    {
1414
        return $this->maxPageLinks;
1415
    }
1416
1417
    public function getFormGroups()
1418
    {
1419
        return $this->formGroups;
1420
    }
1421
1422
    public function setFormGroups(array $formGroups): void
1423
    {
1424
        $this->formGroups = $formGroups;
1425
    }
1426
1427
    public function removeFieldFromFormGroup($key): void
1428
    {
1429
        foreach ($this->formGroups as $name => $formGroup) {
0 ignored issues
show
Bug introduced by
The expression $this->formGroups of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1430
            unset($this->formGroups[$name]['fields'][$key]);
1431
1432
            if (empty($this->formGroups[$name]['fields'])) {
1433
                unset($this->formGroups[$name]);
1434
            }
1435
        }
1436
    }
1437
1438
    /**
1439
     * @param array $group
1440
     */
1441
    public function reorderFormGroup($group, array $keys): void
1442
    {
1443
        $formGroups = $this->getFormGroups();
1444
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1445
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups() on line 1443 can also be of type boolean; however, Sonata\AdminBundle\Admin...tAdmin::setFormGroups() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1446
    }
1447
1448
    public function getFormTabs()
1449
    {
1450
        return $this->formTabs;
1451
    }
1452
1453
    public function setFormTabs(array $formTabs): void
1454
    {
1455
        $this->formTabs = $formTabs;
1456
    }
1457
1458
    public function getShowTabs()
1459
    {
1460
        return $this->showTabs;
1461
    }
1462
1463
    public function setShowTabs(array $showTabs): void
1464
    {
1465
        $this->showTabs = $showTabs;
1466
    }
1467
1468
    public function getShowGroups()
1469
    {
1470
        return $this->showGroups;
1471
    }
1472
1473
    public function setShowGroups(array $showGroups): void
1474
    {
1475
        $this->showGroups = $showGroups;
1476
    }
1477
1478
    public function reorderShowGroup($group, array $keys): void
1479
    {
1480
        $showGroups = $this->getShowGroups();
1481
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1482
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups() on line 1480 can also be of type boolean; however, Sonata\AdminBundle\Admin...tAdmin::setShowGroups() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

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

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

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

Loading history...
1914
    }
1915
1916
    public function setTranslationDomain($translationDomain): void
1917
    {
1918
        $this->translationDomain = $translationDomain;
1919
    }
1920
1921
    public function getTranslationDomain()
1922
    {
1923
        return $this->translationDomain;
1924
    }
1925
1926
    /**
1927
     * {@inheritdoc}
1928
     *
1929
     * NEXT_MAJOR: remove this method
1930
     *
1931
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
1932
     */
1933
    public function setTranslator(TranslatorInterface $translator): void
1934
    {
1935
        $args = \func_get_args();
1936
        if (isset($args[1]) && $args[1]) {
1937
            @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...
1938
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
1939
                E_USER_DEPRECATED
1940
            );
1941
        }
1942
1943
        $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...
1944
    }
1945
1946
    /**
1947
     * {@inheritdoc}
1948
     *
1949
     * NEXT_MAJOR: remove this method
1950
     *
1951
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
1952
     */
1953
    public function getTranslator()
1954
    {
1955
        @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...
1956
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
1957
            E_USER_DEPRECATED
1958
        );
1959
1960
        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...
1961
    }
1962
1963
    public function getTranslationLabel($label, $context = '', $type = '')
1964
    {
1965
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
1966
    }
1967
1968
    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...
1969
    {
1970
        $this->request = $request;
1971
1972
        foreach ($this->getChildren() as $children) {
1973
            $children->setRequest($request);
1974
        }
1975
    }
1976
1977
    public function getRequest()
1978
    {
1979
        if (!$this->request) {
1980
            throw new \RuntimeException('The Request object has not been set');
1981
        }
1982
1983
        return $this->request;
1984
    }
1985
1986
    public function hasRequest()
1987
    {
1988
        return null !== $this->request;
1989
    }
1990
1991
    public function setFormContractor(FormContractorInterface $formBuilder): void
1992
    {
1993
        $this->formContractor = $formBuilder;
1994
    }
1995
1996
    /**
1997
     * @return FormContractorInterface
1998
     */
1999
    public function getFormContractor()
2000
    {
2001
        return $this->formContractor;
2002
    }
2003
2004
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder): void
2005
    {
2006
        $this->datagridBuilder = $datagridBuilder;
2007
    }
2008
2009
    public function getDatagridBuilder()
2010
    {
2011
        return $this->datagridBuilder;
2012
    }
2013
2014
    public function setListBuilder(ListBuilderInterface $listBuilder): void
2015
    {
2016
        $this->listBuilder = $listBuilder;
2017
    }
2018
2019
    public function getListBuilder()
2020
    {
2021
        return $this->listBuilder;
2022
    }
2023
2024
    public function setShowBuilder(ShowBuilderInterface $showBuilder): void
2025
    {
2026
        $this->showBuilder = $showBuilder;
2027
    }
2028
2029
    /**
2030
     * @return ShowBuilderInterface
2031
     */
2032
    public function getShowBuilder()
2033
    {
2034
        return $this->showBuilder;
2035
    }
2036
2037
    public function setConfigurationPool(Pool $configurationPool): void
2038
    {
2039
        $this->configurationPool = $configurationPool;
2040
    }
2041
2042
    /**
2043
     * @return Pool
2044
     */
2045
    public function getConfigurationPool()
2046
    {
2047
        return $this->configurationPool;
2048
    }
2049
2050
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator): void
2051
    {
2052
        $this->routeGenerator = $routeGenerator;
2053
    }
2054
2055
    /**
2056
     * @return RouteGeneratorInterface
2057
     */
2058
    public function getRouteGenerator()
2059
    {
2060
        return $this->routeGenerator;
2061
    }
2062
2063
    public function getCode()
2064
    {
2065
        return $this->code;
2066
    }
2067
2068
    public function getBaseCodeRoute()
2069
    {
2070
        if ($this->isChild()) {
2071
            return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2072
        }
2073
2074
        return $this->getCode();
2075
    }
2076
2077
    public function getModelManager()
2078
    {
2079
        return $this->modelManager;
2080
    }
2081
2082
    public function setModelManager(ModelManagerInterface $modelManager): void
2083
    {
2084
        $this->modelManager = $modelManager;
2085
    }
2086
2087
    public function getManagerType()
2088
    {
2089
        return $this->managerType;
2090
    }
2091
2092
    /**
2093
     * @param string $type
2094
     */
2095
    public function setManagerType($type): void
2096
    {
2097
        $this->managerType = $type;
2098
    }
2099
2100
    public function getObjectIdentifier()
2101
    {
2102
        return $this->getCode();
2103
    }
2104
2105
    /**
2106
     * Set the roles and permissions per role.
2107
     */
2108
    public function setSecurityInformation(array $information): void
2109
    {
2110
        $this->securityInformation = $information;
2111
    }
2112
2113
    public function getSecurityInformation()
2114
    {
2115
        return $this->securityInformation;
2116
    }
2117
2118
    /**
2119
     * Return the list of permissions the user should have in order to display the admin.
2120
     *
2121
     * @param string $context
2122
     *
2123
     * @return array
2124
     */
2125
    public function getPermissionsShow($context)
2126
    {
2127
        switch ($context) {
2128
            case self::CONTEXT_DASHBOARD:
2129
            case self::CONTEXT_MENU:
2130
            default:
2131
                return ['LIST'];
2132
        }
2133
    }
2134
2135
    public function showIn($context)
2136
    {
2137
        switch ($context) {
2138
            case self::CONTEXT_DASHBOARD:
2139
            case self::CONTEXT_MENU:
2140
            default:
2141
                return $this->isGranted($this->getPermissionsShow($context));
0 ignored issues
show
Documentation introduced by
$this->getPermissionsShow($context) is of type array<integer,string,{"0":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2142
        }
2143
    }
2144
2145
    public function createObjectSecurity($object): void
2146
    {
2147
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2148
    }
2149
2150
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler): void
2151
    {
2152
        $this->securityHandler = $securityHandler;
2153
    }
2154
2155
    public function getSecurityHandler()
2156
    {
2157
        return $this->securityHandler;
2158
    }
2159
2160
    public function isGranted($name, $object = null)
2161
    {
2162
        $key = md5(json_encode($name).($object ? '/'.spl_object_hash($object) : ''));
2163
2164
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
2165
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2166
        }
2167
2168
        return $this->cacheIsGranted[$key];
2169
    }
2170
2171
    public function getUrlsafeIdentifier($entity)
2172
    {
2173
        return $this->getModelManager()->getUrlsafeIdentifier($entity);
2174
    }
2175
2176
    public function getNormalizedIdentifier($entity)
2177
    {
2178
        return $this->getModelManager()->getNormalizedIdentifier($entity);
2179
    }
2180
2181
    public function id($entity)
2182
    {
2183
        return $this->getNormalizedIdentifier($entity);
2184
    }
2185
2186
    public function setValidator(ValidatorInterface $validator): void
2187
    {
2188
        $this->validator = $validator;
2189
    }
2190
2191
    public function getValidator()
2192
    {
2193
        return $this->validator;
2194
    }
2195
2196
    public function getShow()
2197
    {
2198
        $this->buildShow();
2199
2200
        return $this->show;
2201
    }
2202
2203
    public function setFormTheme(array $formTheme): void
2204
    {
2205
        $this->formTheme = $formTheme;
2206
    }
2207
2208
    public function getFormTheme()
2209
    {
2210
        return $this->formTheme;
2211
    }
2212
2213
    public function setFilterTheme(array $filterTheme): void
2214
    {
2215
        $this->filterTheme = $filterTheme;
2216
    }
2217
2218
    public function getFilterTheme()
2219
    {
2220
        return $this->filterTheme;
2221
    }
2222
2223
    public function addExtension(AdminExtensionInterface $extension): void
2224
    {
2225
        $this->extensions[] = $extension;
2226
    }
2227
2228
    public function getExtensions()
2229
    {
2230
        return $this->extensions;
2231
    }
2232
2233
    public function setMenuFactory(MenuFactoryInterface $menuFactory): void
2234
    {
2235
        $this->menuFactory = $menuFactory;
2236
    }
2237
2238
    public function getMenuFactory()
2239
    {
2240
        return $this->menuFactory;
2241
    }
2242
2243
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder): void
2244
    {
2245
        $this->routeBuilder = $routeBuilder;
2246
    }
2247
2248
    public function getRouteBuilder()
2249
    {
2250
        return $this->routeBuilder;
2251
    }
2252
2253
    public function toString($object)
2254
    {
2255
        if (!\is_object($object)) {
2256
            return '';
2257
        }
2258
2259
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2260
            return (string) $object;
2261
        }
2262
2263
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2264
    }
2265
2266
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy): void
2267
    {
2268
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2269
    }
2270
2271
    public function getLabelTranslatorStrategy()
2272
    {
2273
        return $this->labelTranslatorStrategy;
2274
    }
2275
2276
    public function supportsPreviewMode()
2277
    {
2278
        return $this->supportsPreviewMode;
2279
    }
2280
2281
    /**
2282
     * Set custom per page options.
2283
     */
2284
    public function setPerPageOptions(array $options): void
2285
    {
2286
        $this->perPageOptions = $options;
2287
    }
2288
2289
    /**
2290
     * Returns predefined per page options.
2291
     *
2292
     * @return array
2293
     */
2294
    public function getPerPageOptions()
2295
    {
2296
        return $this->perPageOptions;
2297
    }
2298
2299
    /**
2300
     * Set pager type.
2301
     *
2302
     * @param string $pagerType
2303
     */
2304
    public function setPagerType($pagerType): void
2305
    {
2306
        $this->pagerType = $pagerType;
2307
    }
2308
2309
    /**
2310
     * Get pager type.
2311
     *
2312
     * @return string
2313
     */
2314
    public function getPagerType()
2315
    {
2316
        return $this->pagerType;
2317
    }
2318
2319
    /**
2320
     * Returns true if the per page value is allowed, false otherwise.
2321
     *
2322
     * @param int $perPage
2323
     *
2324
     * @return bool
2325
     */
2326
    public function determinedPerPageValue($perPage)
2327
    {
2328
        return \in_array($perPage, $this->perPageOptions, true);
2329
    }
2330
2331
    public function isAclEnabled()
2332
    {
2333
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2334
    }
2335
2336
    public function getObjectMetadata($object)
2337
    {
2338
        return new Metadata($this->toString($object));
2339
    }
2340
2341
    public function getListModes()
2342
    {
2343
        return $this->listModes;
2344
    }
2345
2346
    public function setListMode($mode): void
2347
    {
2348
        if (!$this->hasRequest()) {
2349
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2350
        }
2351
2352
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2353
    }
2354
2355
    public function getListMode()
2356
    {
2357
        if (!$this->hasRequest()) {
2358
            return 'list';
2359
        }
2360
2361
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2362
    }
2363
2364
    public function getAccessMapping()
2365
    {
2366
        return $this->accessMapping;
2367
    }
2368
2369
    public function checkAccess($action, $object = null): void
2370
    {
2371
        $access = $this->getAccess();
2372
2373
        if (!\array_key_exists($action, $access)) {
2374
            throw new \InvalidArgumentException(sprintf(
2375
                'Action "%s" could not be found in access mapping.'
2376
                .' Please make sure your action is defined into your admin class accessMapping property.',
2377
                $action
2378
            ));
2379
        }
2380
2381
        if (!\is_array($access[$action])) {
2382
            $access[$action] = [$access[$action]];
2383
        }
2384
2385
        foreach ($access[$action] as $role) {
2386
            if (false === $this->isGranted($role, $object)) {
2387
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2388
            }
2389
        }
2390
    }
2391
2392
    /**
2393
     * {@inheritdoc}
2394
     */
2395
    public function hasAccess(string $action, ?object $object = null): bool
2396
    {
2397
        $access = $this->getAccess();
2398
2399
        if (!\array_key_exists($action, $access)) {
2400
            return false;
2401
        }
2402
2403
        if (!\is_array($access[$action])) {
2404
            $access[$action] = [$access[$action]];
2405
        }
2406
2407
        foreach ($access[$action] as $role) {
2408
            if (false === $this->isGranted($role, $object)) {
2409
                return false;
2410
            }
2411
        }
2412
2413
        return true;
2414
    }
2415
2416
    /**
2417
     * @param object|null $object
2418
     */
2419
    final public function getActionButtons(string $action, $object = null): array
2420
    {
2421
        $buttonList = [];
2422
2423
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
2424
            && $this->hasAccess('create')
2425
            && $this->hasRoute('create')
2426
        ) {
2427
            $buttonList['create'] = [
2428
                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2429
            ];
2430
        }
2431
2432
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
2433
            && $this->canAccessObject('edit', $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...
2434
            && $this->hasRoute('edit')
2435
        ) {
2436
            $buttonList['edit'] = [
2437
                'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2438
            ];
2439
        }
2440
2441
        if (\in_array($action, ['show', 'edit', 'acl'], true)
2442
            && $this->canAccessObject('history', $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...
2443
            && $this->hasRoute('history')
2444
        ) {
2445
            $buttonList['history'] = [
2446
                'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2447
            ];
2448
        }
2449
2450
        if (\in_array($action, ['edit', 'history'], true)
2451
            && $this->isAclEnabled()
2452
            && $this->canAccessObject('acl', $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...
2453
            && $this->hasRoute('acl')
2454
        ) {
2455
            $buttonList['acl'] = [
2456
                'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2457
            ];
2458
        }
2459
2460
        if (\in_array($action, ['edit', 'history', 'acl'], true)
2461
            && $this->canAccessObject('show', $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...
2462
            && \count($this->getShow()) > 0
2463
            && $this->hasRoute('show')
2464
        ) {
2465
            $buttonList['show'] = [
2466
                'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2467
            ];
2468
        }
2469
2470
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
2471
            && $this->hasAccess('list')
2472
            && $this->hasRoute('list')
2473
        ) {
2474
            $buttonList['list'] = [
2475
                'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2476
            ];
2477
        }
2478
2479
        $buttonList = $this->configureActionButtons($buttonList, $action, $object);
2480
2481
        foreach ($this->getExtensions() as $extension) {
2482
            $buttonList = $extension->configureActionButtons($this, $buttonList, $action, $object);
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 2419 can also be of type null; however, Sonata\AdminBundle\Admin...onfigureActionButtons() does only seem to accept object, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2483
        }
2484
2485
        return $buttonList;
2486
    }
2487
2488
    /**
2489
     * {@inheritdoc}
2490
     */
2491
    public function getDashboardActions()
2492
    {
2493
        $actions = [];
2494
2495
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2496
            $actions['create'] = [
2497
                'label' => 'link_add',
2498
                'translation_domain' => 'SonataAdminBundle',
2499
                'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2500
                'url' => $this->generateUrl('create'),
2501
                'icon' => 'plus-circle',
2502
            ];
2503
        }
2504
2505
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2506
            $actions['list'] = [
2507
                'label' => 'link_list',
2508
                'translation_domain' => 'SonataAdminBundle',
2509
                'url' => $this->generateUrl('list'),
2510
                'icon' => 'list',
2511
            ];
2512
        }
2513
2514
        return $actions;
2515
    }
2516
2517
    /**
2518
     * {@inheritdoc}
2519
     */
2520
    final public function showMosaicButton($isShown): void
2521
    {
2522
        if ($isShown) {
2523
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
2524
        } else {
2525
            unset($this->listModes['mosaic']);
2526
        }
2527
    }
2528
2529
    /**
2530
     * @param object $object
2531
     */
2532
    final public function getSearchResultLink($object): ?string
2533
    {
2534
        foreach ($this->searchResultActions as $action) {
2535
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2536
                return $this->generateObjectUrl($action, $object);
2537
            }
2538
        }
2539
2540
        return null;
2541
    }
2542
2543
    /**
2544
     * Checks if a filter type is set to a default value.
2545
     */
2546
    final public function isDefaultFilter(string $name): bool
2547
    {
2548
        $filter = $this->getFilterParameters();
2549
        $default = $this->getDefaultFilterValues();
2550
2551
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
2552
            return false;
2553
        }
2554
2555
        return $filter[$name] === $default[$name];
2556
    }
2557
2558
    public function canAccessObject(string $action, object $object): bool
2559
    {
2560
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2561
    }
2562
2563
    public function configureActionButtons(array $buttonList, string $action, ?object $object = null): array
2564
    {
2565
        return $buttonList;
2566
    }
2567
2568
    /**
2569
     * Hook to run after initilization.
2570
     */
2571
    protected function configure(): void
2572
    {
2573
    }
2574
2575
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
2576
    {
2577
        return $query;
2578
    }
2579
2580
    /**
2581
     * urlize the given word.
2582
     *
2583
     * @param string $word
2584
     * @param string $sep  the separator
2585
     *
2586
     * @return string
2587
     */
2588
    final protected function urlize($word, $sep = '_')
2589
    {
2590
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
2591
    }
2592
2593
    final protected function getTemplateRegistry(): MutableTemplateRegistryInterface
2594
    {
2595
        return $this->templateRegistry;
2596
    }
2597
2598
    /**
2599
     * Returns a list of default filters.
2600
     *
2601
     * @return array
2602
     */
2603
    final protected function getDefaultFilterValues()
2604
    {
2605
        $defaultFilterValues = [];
2606
2607
        $this->configureDefaultFilterValues($defaultFilterValues);
2608
2609
        foreach ($this->getExtensions() as $extension) {
2610
            // NEXT_MAJOR: remove method check
2611
            if (method_exists($extension, 'configureDefaultFilterValues')) {
2612
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2613
            }
2614
        }
2615
2616
        return $defaultFilterValues;
2617
    }
2618
2619
    protected function configureFormFields(FormMapper $form): void
2620
    {
2621
    }
2622
2623
    protected function configureListFields(ListMapper $list): void
2624
    {
2625
    }
2626
2627
    protected function configureDatagridFilters(DatagridMapper $filter): void
2628
    {
2629
    }
2630
2631
    protected function configureShowFields(ShowMapper $show): void
2632
    {
2633
    }
2634
2635
    protected function configureRoutes(RouteCollection $collection): void
2636
    {
2637
    }
2638
2639
    /**
2640
     * Allows you to customize batch actions.
2641
     *
2642
     * @param array $actions List of actions
2643
     *
2644
     * @return array
2645
     */
2646
    protected function configureBatchActions($actions)
2647
    {
2648
        return $actions;
2649
    }
2650
2651
    /**
2652
     * NEXT_MAJOR: remove this method.
2653
     *
2654
     * @deprecated Use configureTabMenu instead
2655
     */
2656
    protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
0 ignored issues
show
Unused Code introduced by
The parameter $menu is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $action is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $childAdmin is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2657
    {
2658
    }
2659
2660
    /**
2661
     * Configures the tab menu in your admin.
2662
     *
2663
     * @param string $action
2664
     */
2665
    protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
2666
    {
2667
        // Use configureSideMenu not to mess with previous overrides
2668
        // NEXT_MAJOR: remove this line
2669
        $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...
2670
    }
2671
2672
    /**
2673
     * build the view FieldDescription array.
2674
     */
2675
    protected function buildShow(): void
2676
    {
2677
        if ($this->show) {
2678
            return;
2679
        }
2680
2681
        $this->show = new FieldDescriptionCollection();
2682
        $mapper = new ShowMapper($this->showBuilder, $this->show, $this);
2683
2684
        $this->configureShowFields($mapper);
2685
2686
        foreach ($this->getExtensions() as $extension) {
2687
            $extension->configureShowFields($mapper);
2688
        }
2689
    }
2690
2691
    /**
2692
     * build the list FieldDescription array.
2693
     */
2694
    protected function buildList(): void
2695
    {
2696
        if ($this->list) {
2697
            return;
2698
        }
2699
2700
        $this->list = $this->getListBuilder()->getBaseList();
2701
2702
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
2703
2704
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
2705
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2706
                $this->getClass(),
2707
                'batch',
2708
                [
2709
                    'label' => 'batch',
2710
                    'code' => '_batch',
2711
                    'sortable' => false,
2712
                    'virtual_field' => true,
2713
                ]
2714
            );
2715
2716
            $fieldDescription->setAdmin($this);
2717
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
2718
2719
            $mapper->add($fieldDescription, 'batch');
2720
        }
2721
2722
        $this->configureListFields($mapper);
2723
2724
        foreach ($this->getExtensions() as $extension) {
2725
            $extension->configureListFields($mapper);
2726
        }
2727
2728
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
2729
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2730
                $this->getClass(),
2731
                'select',
2732
                [
2733
                    'label' => false,
2734
                    'code' => '_select',
2735
                    'sortable' => false,
2736
                    'virtual_field' => false,
2737
                ]
2738
            );
2739
2740
            $fieldDescription->setAdmin($this);
2741
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
2742
2743
            $mapper->add($fieldDescription, 'select');
2744
        }
2745
    }
2746
2747
    /**
2748
     * Build the form FieldDescription collection.
2749
     */
2750
    protected function buildForm(): void
2751
    {
2752
        if ($this->form) {
2753
            return;
2754
        }
2755
2756
        // append parent object if any
2757
        // todo : clean the way the Admin class can retrieve set the object
2758
        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...
2759
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
2760
2761
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
2762
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
2763
2764
            $object = $this->getSubject();
2765
2766
            $value = $propertyAccessor->getValue($object, $propertyPath);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 2764 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...
2767
2768
            if (\is_array($value) || $value instanceof \ArrayAccess) {
2769
                $value[] = $parent;
2770
                $propertyAccessor->setValue($object, $propertyPath, $value);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 2764 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...
2771
            } else {
2772
                $propertyAccessor->setValue($object, $propertyPath, $parent);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 2764 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...
2773
            }
2774
        }
2775
2776
        $formBuilder = $this->getFormBuilder();
2777
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
2778
            $this->preValidate($event->getData());
2779
        }, 100);
2780
2781
        $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...
2782
    }
2783
2784
    /**
2785
     * Gets the subclass corresponding to the given name.
2786
     *
2787
     * @param string $name The name of the sub class
2788
     *
2789
     * @return string the subclass
2790
     */
2791
    protected function getSubClass($name)
2792
    {
2793
        if ($this->hasSubClass($name)) {
2794
            return $this->subClasses[$name];
2795
        }
2796
2797
        throw new \RuntimeException(sprintf(
2798
            'Unable to find the subclass `%s` for admin `%s`',
2799
            $name,
2800
            static::class
2801
        ));
2802
    }
2803
2804
    /**
2805
     * Attach the inline validator to the model metadata, this must be done once per admin.
2806
     */
2807
    protected function attachInlineValidator(): void
2808
    {
2809
        $admin = $this;
2810
2811
        // add the custom inline validation option
2812
        $metadata = $this->validator->getMetadataFor($this->getClass());
2813
2814
        $metadata->addConstraint(new InlineConstraint([
2815
            'service' => $this,
2816
            'method' => static function (ErrorElement $errorElement, $object) use ($admin): void {
2817
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
2818
2819
                // This avoid the main validation to be cascaded to children
2820
                // The problem occurs when a model Page has a collection of Page as property
2821
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
2822
                    return;
2823
                }
2824
2825
                $admin->validate($errorElement, $object);
2826
2827
                foreach ($admin->getExtensions() as $extension) {
2828
                    $extension->validate($admin, $errorElement, $object);
2829
                }
2830
            },
2831
            'serializingWarning' => true,
2832
        ]));
2833
    }
2834
2835
    /**
2836
     * Predefine per page options.
2837
     */
2838
    protected function predefinePerPageOptions(): void
2839
    {
2840
        array_unshift($this->perPageOptions, $this->maxPerPage);
2841
        $this->perPageOptions = array_unique($this->perPageOptions);
2842
        sort($this->perPageOptions);
2843
    }
2844
2845
    /**
2846
     * Return list routes with permissions name.
2847
     *
2848
     * @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...
2849
     */
2850
    protected function getAccess()
2851
    {
2852
        $access = array_merge([
2853
            'acl' => 'MASTER',
2854
            'export' => 'EXPORT',
2855
            'historyCompareRevisions' => 'EDIT',
2856
            'historyViewRevision' => 'EDIT',
2857
            'history' => 'EDIT',
2858
            'edit' => 'EDIT',
2859
            'show' => 'VIEW',
2860
            'create' => 'CREATE',
2861
            'delete' => 'DELETE',
2862
            'batchDelete' => 'DELETE',
2863
            'list' => 'LIST',
2864
        ], $this->getAccessMapping());
2865
2866
        foreach ($this->extensions as $extension) {
2867
            $access = array_merge($access, $extension->getAccessMapping($this));
2868
        }
2869
2870
        return $access;
2871
    }
2872
2873
    /**
2874
     * Configures a list of default filters.
2875
     */
2876
    protected function configureDefaultFilterValues(array &$filterValues): void
2877
    {
2878
    }
2879
2880
    /**
2881
     * {@inheritdoc}
2882
     */
2883
    private function buildDatagrid(): void
2884
    {
2885
        if ($this->datagrid) {
2886
            return;
2887
        }
2888
2889
        $filterParameters = $this->getFilterParameters();
2890
2891
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
2892
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
2893
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
2894
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
2895
            } else {
2896
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
2897
                    $this->getClass(),
2898
                    $filterParameters['_sort_by'],
2899
                    []
2900
                );
2901
2902
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
2903
            }
2904
        }
2905
2906
        // initialize the datagrid
2907
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
2908
2909
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
2910
2911
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
2912
2913
        // build the datagrid filter
2914
        $this->configureDatagridFilters($mapper);
2915
2916
        // ok, try to limit to add parent filter
2917
        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...
2918
            $mapper->add($this->getParentAssociationMapping(), null, [
2919
                'show_filter' => false,
2920
                'label' => false,
2921
                'field_type' => ModelHiddenType::class,
2922
                'field_options' => [
2923
                    'model_manager' => $this->getModelManager(),
2924
                ],
2925
                'operator_type' => HiddenType::class,
2926
            ], null, null, [
2927
                'admin_code' => $this->getParent()->getCode(),
2928
            ]);
2929
        }
2930
2931
        foreach ($this->getExtensions() as $extension) {
2932
            $extension->configureDatagridFilters($mapper);
2933
        }
2934
    }
2935
2936
    /**
2937
     * Build all the related urls to the current admin.
2938
     */
2939
    private function buildRoutes(): void
2940
    {
2941
        if ($this->loaded['routes']) {
2942
            return;
2943
        }
2944
2945
        $this->loaded['routes'] = true;
2946
2947
        $this->routes = new RouteCollection(
2948
            $this->getBaseCodeRoute(),
2949
            $this->getBaseRouteName(),
2950
            $this->getBaseRoutePattern(),
2951
            $this->getBaseControllerName()
2952
        );
2953
2954
        $this->routeBuilder->build($this, $this->routes);
2955
2956
        $this->configureRoutes($this->routes);
2957
2958
        foreach ($this->getExtensions() as $extension) {
2959
            $extension->configureRoutes($this, $this->routes);
2960
        }
2961
    }
2962
}
2963
2964
class_exists(\Sonata\Form\Validator\ErrorElement::class);
2965