Completed
Pull Request — master (#6210)
by Jordi Sala
37:29
created

AbstractAdmin::getFilterParameters()   B

Complexity

Conditions 10
Paths 81

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 50
rs 7.2242
c 0
b 0
f 0
cc 10
nc 81
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Admin;
15
16
use Doctrine\Common\Util\ClassUtils;
17
use Knp\Menu\FactoryInterface;
18
use Knp\Menu\ItemInterface;
19
use Sonata\AdminBundle\Builder\DatagridBuilderInterface;
20
use Sonata\AdminBundle\Builder\FormContractorInterface;
21
use Sonata\AdminBundle\Builder\ListBuilderInterface;
22
use Sonata\AdminBundle\Builder\RouteBuilderInterface;
23
use Sonata\AdminBundle\Builder\ShowBuilderInterface;
24
use Sonata\AdminBundle\Datagrid\DatagridInterface;
25
use Sonata\AdminBundle\Datagrid\DatagridMapper;
26
use Sonata\AdminBundle\Datagrid\ListMapper;
27
use Sonata\AdminBundle\Datagrid\Pager;
28
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
29
use Sonata\AdminBundle\Filter\Persister\FilterPersisterInterface;
30
use Sonata\AdminBundle\Form\FormMapper;
31
use Sonata\AdminBundle\Form\Type\ModelHiddenType;
32
use Sonata\AdminBundle\Manipulator\ObjectManipulator;
33
use Sonata\AdminBundle\Model\ModelManagerInterface;
34
use Sonata\AdminBundle\Object\Metadata;
35
use Sonata\AdminBundle\Object\MetadataInterface;
36
use Sonata\AdminBundle\Route\RouteCollection;
37
use Sonata\AdminBundle\Route\RouteGeneratorInterface;
38
use Sonata\AdminBundle\Security\Handler\AclSecurityHandlerInterface;
39
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
40
use Sonata\AdminBundle\Show\ShowMapper;
41
use Sonata\AdminBundle\Templating\MutableTemplateRegistryInterface;
42
use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface;
43
use Sonata\Exporter\Source\SourceIteratorInterface;
44
use Sonata\Form\Validator\Constraints\InlineConstraint;
45
use Sonata\Form\Validator\ErrorElement;
46
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
47
use Symfony\Component\Form\Form;
48
use Symfony\Component\Form\FormBuilderInterface;
49
use Symfony\Component\Form\FormEvent;
50
use Symfony\Component\Form\FormEvents;
51
use Symfony\Component\Form\FormInterface;
52
use Symfony\Component\HttpFoundation\Request;
53
use Symfony\Component\PropertyAccess\PropertyPath;
54
use Symfony\Component\Routing\Generator\UrlGeneratorInterface as RoutingUrlGeneratorInterface;
55
use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
56
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
57
use Symfony\Component\Validator\Mapping\GenericMetadata;
58
use Symfony\Component\Validator\Validator\ValidatorInterface;
59
60
/**
61
 * @author Thomas Rabaix <[email protected]>
62
 */
63
abstract class AbstractAdmin implements AdminInterface, DomainObjectInterface, AdminTreeInterface
64
{
65
    public const CONTEXT_MENU = 'menu';
66
    public const CONTEXT_DASHBOARD = 'dashboard';
67
68
    public const CLASS_REGEX =
69
        '@
70
        (?:([A-Za-z0-9]*)\\\)?        # vendor name / app name
71
        (Bundle\\\)?                  # optional bundle directory
72
        ([A-Za-z0-9]+?)(?:Bundle)?\\\ # bundle name, with optional suffix
73
        (
74
            Entity|Document|Model|PHPCR|CouchDocument|Phpcr|
75
            Doctrine\\\Orm|Doctrine\\\Phpcr|Doctrine\\\MongoDB|Doctrine\\\CouchDB
76
        )\\\(.*)@x';
77
78
    public const MOSAIC_ICON_CLASS = 'fa fa-th-large fa-fw';
79
80
    /**
81
     * The list FieldDescription constructed from the configureListField method.
82
     *
83
     * @var FieldDescriptionInterface[]
84
     */
85
    protected $listFieldDescriptions = [];
86
87
    /**
88
     * The show FieldDescription constructed from the configureShowFields method.
89
     *
90
     * @var FieldDescriptionInterface[]
91
     */
92
    protected $showFieldDescriptions = [];
93
94
    /**
95
     * The list FieldDescription constructed from the configureFormField method.
96
     *
97
     * @var FieldDescriptionInterface[]
98
     */
99
    protected $formFieldDescriptions = [];
100
101
    /**
102
     * The filter FieldDescription constructed from the configureFilterField method.
103
     *
104
     * @var FieldDescriptionInterface[]
105
     */
106
    protected $filterFieldDescriptions = [];
107
108
    /**
109
     * The maximum number of page numbers to display in the list.
110
     *
111
     * @var int
112
     */
113
    protected $maxPageLinks = 25;
114
115
    /**
116
     * The base route name used to generate the routing information.
117
     *
118
     * @var string
119
     */
120
    protected $baseRouteName;
121
122
    /**
123
     * The base route pattern used to generate the routing information.
124
     *
125
     * @var string
126
     */
127
    protected $baseRoutePattern;
128
129
    /**
130
     * The base name controller used to generate the routing information.
131
     *
132
     * @var string
133
     */
134
    protected $baseControllerName;
135
136
    /**
137
     * The label class name  (used in the title/breadcrumb ...).
138
     *
139
     * @var string
140
     */
141
    protected $classnameLabel;
142
143
    /**
144
     * The translation domain to be used to translate messages.
145
     *
146
     * @var string
147
     */
148
    protected $translationDomain = 'messages';
149
150
    /**
151
     * Options to set to the form (ie, validation_groups).
152
     *
153
     * @var array
154
     */
155
    protected $formOptions = [];
156
157
    /**
158
     * Pager type.
159
     *
160
     * @var string
161
     */
162
    protected $pagerType = Pager::TYPE_DEFAULT;
163
164
    /**
165
     * The code related to the admin.
166
     *
167
     * @var string
168
     */
169
    protected $code;
170
171
    /**
172
     * The label.
173
     *
174
     * @var string
175
     */
176
    protected $label;
177
178
    /**
179
     * Array of routes related to this admin.
180
     *
181
     * @var RouteCollection
182
     */
183
    protected $routes;
184
185
    /**
186
     * The subject only set in edit/update/create mode.
187
     *
188
     * @var object|null
189
     */
190
    protected $subject;
191
192
    /**
193
     * Define a Collection of child admin, ie /admin/order/{id}/order-element/{childId}.
194
     *
195
     * @var array
196
     */
197
    protected $children = [];
198
199
    /**
200
     * Reference the parent admin.
201
     *
202
     * @var AdminInterface|null
203
     */
204
    protected $parent;
205
206
    /**
207
     * Reference the parent FieldDescription related to this admin
208
     * only set for FieldDescription which is associated to an Sub Admin instance.
209
     *
210
     * @var FieldDescriptionInterface
211
     */
212
    protected $parentFieldDescription;
213
214
    /**
215
     * If true then the current admin is part of the nested admin set (from the url).
216
     *
217
     * @var bool
218
     */
219
    protected $currentChild = false;
220
221
    /**
222
     * The uniqid is used to avoid clashing with 2 admin related to the code
223
     * ie: a Block linked to a Block.
224
     *
225
     * @var string
226
     */
227
    protected $uniqid;
228
229
    /**
230
     * The Entity or Document manager.
231
     *
232
     * @var ModelManagerInterface
233
     */
234
    protected $modelManager;
235
236
    /**
237
     * The current request object.
238
     *
239
     * @var Request|null
240
     */
241
    protected $request;
242
243
    /**
244
     * The related form contractor.
245
     *
246
     * @var FormContractorInterface
247
     */
248
    protected $formContractor;
249
250
    /**
251
     * The related list builder.
252
     *
253
     * @var ListBuilderInterface
254
     */
255
    protected $listBuilder;
256
257
    /**
258
     * The related view builder.
259
     *
260
     * @var ShowBuilderInterface
261
     */
262
    protected $showBuilder;
263
264
    /**
265
     * The related datagrid builder.
266
     *
267
     * @var DatagridBuilderInterface
268
     */
269
    protected $datagridBuilder;
270
271
    /**
272
     * @var RouteBuilderInterface
273
     */
274
    protected $routeBuilder;
275
276
    /**
277
     * The datagrid instance.
278
     *
279
     * @var DatagridInterface|null
280
     */
281
    protected $datagrid;
282
283
    /**
284
     * The router instance.
285
     *
286
     * @var RouteGeneratorInterface|null
287
     */
288
    protected $routeGenerator;
289
290
    /**
291
     * @var SecurityHandlerInterface
292
     */
293
    protected $securityHandler;
294
295
    /**
296
     * @var ValidatorInterface
297
     */
298
    protected $validator;
299
300
    /**
301
     * The configuration pool.
302
     *
303
     * @var Pool
304
     */
305
    protected $configurationPool;
306
307
    /**
308
     * @var ItemInterface
309
     */
310
    protected $menu;
311
312
    /**
313
     * @var FactoryInterface
314
     */
315
    protected $menuFactory;
316
317
    /**
318
     * @var array<string, bool>
319
     */
320
    protected $loaded = [
321
        'routes' => false,
322
        'tab_menu' => false,
323
        'show' => false,
324
        'list' => false,
325
        'form' => false,
326
        'datagrid' => false,
327
    ];
328
329
    /**
330
     * @var string[]
331
     */
332
    protected $formTheme = [];
333
334
    /**
335
     * @var string[]
336
     */
337
    protected $filterTheme = [];
338
339
    /**
340
     * @var AdminExtensionInterface[]
341
     */
342
    protected $extensions = [];
343
344
    /**
345
     * @var LabelTranslatorStrategyInterface
346
     */
347
    protected $labelTranslatorStrategy;
348
349
    /**
350
     * Setting to true will enable preview mode for
351
     * the entity and show a preview button in the
352
     * edit/create forms.
353
     *
354
     * @var bool
355
     */
356
    protected $supportsPreviewMode = false;
357
358
    /**
359
     * Roles and permissions per role.
360
     *
361
     * @var array 'role' => ['permission', 'permission']
362
     */
363
    protected $securityInformation = [];
364
365
    protected $cacheIsGranted = [];
366
367
    /**
368
     * Action list for the search result.
369
     *
370
     * @var string[]
371
     */
372
    protected $searchResultActions = ['edit', 'show'];
373
374
    protected $listModes = [
375
        'list' => [
376
            'class' => 'fa fa-list fa-fw',
377
        ],
378
        'mosaic' => [
379
            'class' => self::MOSAIC_ICON_CLASS,
380
        ],
381
    ];
382
383
    /**
384
     * The Access mapping.
385
     *
386
     * @var array [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
387
     */
388
    protected $accessMapping = [];
389
390
    /**
391
     * @var array
392
     */
393
    private $parentAssociationMapping = [];
394
395
    /**
396
     * @var MutableTemplateRegistryInterface
397
     */
398
    private $templateRegistry;
399
400
    /**
401
     * The class name managed by the admin class.
402
     *
403
     * @var string
404
     */
405
    private $class;
406
407
    /**
408
     * The subclasses supported by the admin class.
409
     *
410
     * @var array<string, string>
411
     */
412
    private $subClasses = [];
413
414
    /**
415
     * The list collection.
416
     *
417
     * @var FieldDescriptionCollection|null
418
     */
419
    private $list;
420
421
    /**
422
     * @var FieldDescriptionCollection|null
423
     */
424
    private $show;
425
426
    /**
427
     * @var Form|null
428
     */
429
    private $form;
430
431
    /**
432
     * The cached base route name.
433
     *
434
     * @var string
435
     */
436
    private $cachedBaseRouteName;
437
438
    /**
439
     * The cached base route pattern.
440
     *
441
     * @var string
442
     */
443
    private $cachedBaseRoutePattern;
444
445
    /**
446
     * The form group disposition.
447
     *
448
     * @var array<string, mixed>
449
     */
450
    private $formGroups = [];
451
452
    /**
453
     * The form tabs disposition.
454
     *
455
     * @var array<string, mixed>
456
     */
457
    private $formTabs = [];
458
459
    /**
460
     * The view group disposition.
461
     *
462
     * @var array<string, mixed>
463
     */
464
    private $showGroups = [];
465
466
    /**
467
     * The view tab disposition.
468
     *
469
     * @var array<string, mixed>
470
     */
471
    private $showTabs = [];
472
473
    /**
474
     * The manager type to use for the admin.
475
     *
476
     * @var string
477
     */
478
    private $managerType;
479
480
    /**
481
     * Component responsible for persisting filters.
482
     *
483
     * @var FilterPersisterInterface|null
484
     */
485
    private $filterPersister;
486
487
    public function __construct(string $code, string $class, ?string $baseControllerName = null)
488
    {
489
        $this->code = $code;
490
        $this->class = $class;
491
        $this->baseControllerName = $baseControllerName;
492
    }
493
494
    /**
495
     * {@inheritdoc}
496
     */
497
    public function getExportFormats(): array
498
    {
499
        return [
500
            'json', 'xml', 'csv', 'xls',
501
        ];
502
    }
503
504
    /**
505
     * {@inheritdoc}
506
     */
507
    public function getExportFields(): array
508
    {
509
        $fields = $this->getModelManager()->getExportFields($this->getClass());
510
511
        foreach ($this->getExtensions() as $extension) {
512
            if (method_exists($extension, 'configureExportFields')) {
513
                $fields = $extension->configureExportFields($this, $fields);
514
            }
515
        }
516
517
        return $fields;
518
    }
519
520
    public function getDataSourceIterator(): SourceIteratorInterface
521
    {
522
        $datagrid = $this->getDatagrid();
523
        $datagrid->buildPager();
524
525
        $fields = [];
526
527
        foreach ($this->getExportFields() as $key => $field) {
528
            $fields[$key] = $this->getTranslationLabel($field, 'export', 'label');
529
        }
530
531
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
532
    }
533
534
    public function validate(ErrorElement $errorElement, $object): void
0 ignored issues
show
Unused Code introduced by
The parameter $errorElement 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 $object 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...
535
    {
536
    }
537
538
    /**
539
     * define custom variable.
540
     */
541
    public function initialize(): void
542
    {
543
        if (!$this->classnameLabel) {
544
            $this->classnameLabel = substr($this->getClass(), strrpos($this->getClass(), '\\') + 1);
545
        }
546
547
        $this->configure();
548
    }
549
550
    public function update(object $object): object
551
    {
552
        $this->preUpdate($object);
553
        foreach ($this->extensions as $extension) {
554
            $extension->preUpdate($this, $object);
555
        }
556
557
        $result = $this->getModelManager()->update($object);
558
        // BC compatibility
559
        if (null !== $result) {
560
            $object = $result;
561
        }
562
563
        $this->postUpdate($object);
564
        foreach ($this->extensions as $extension) {
565
            $extension->postUpdate($this, $object);
566
        }
567
568
        return $object;
569
    }
570
571
    public function create(object $object): object
572
    {
573
        $this->prePersist($object);
574
        foreach ($this->extensions as $extension) {
575
            $extension->prePersist($this, $object);
576
        }
577
578
        $result = $this->getModelManager()->create($object);
579
        // BC compatibility
580
        if (null !== $result) {
581
            $object = $result;
582
        }
583
584
        $this->postPersist($object);
585
        foreach ($this->extensions as $extension) {
586
            $extension->postPersist($this, $object);
587
        }
588
589
        $this->createObjectSecurity($object);
590
591
        return $object;
592
    }
593
594
    public function delete(object $object): void
595
    {
596
        $this->preRemove($object);
597
        foreach ($this->extensions as $extension) {
598
            $extension->preRemove($this, $object);
599
        }
600
601
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
602
        $this->getModelManager()->delete($object);
603
604
        $this->postRemove($object);
605
        foreach ($this->extensions as $extension) {
606
            $extension->postRemove($this, $object);
607
        }
608
    }
609
610
    public function preValidate(object $object): void
611
    {
612
    }
613
614
    public function preUpdate(object $object): void
615
    {
616
    }
617
618
    public function postUpdate(object $object): void
619
    {
620
    }
621
622
    public function prePersist(object $object): void
623
    {
624
    }
625
626
    public function postPersist(object $object): void
627
    {
628
    }
629
630
    public function preRemove(object $object): void
631
    {
632
    }
633
634
    public function postRemove(object $object): void
635
    {
636
    }
637
638
    public function preBatchAction(string $actionName, ProxyQueryInterface $query, array &$idx, bool $allElements = false): void
639
    {
640
    }
641
642
    public function getFilterParameters(): array
643
    {
644
        $parameters = [];
645
646
        // build the values array
647
        if ($this->hasRequest()) {
648
            $filters = $this->request->query->get('filter', []);
649
            if (isset($filters['_page'])) {
650
                $filters['_page'] = (int) $filters['_page'];
651
            }
652
            if (isset($filters['_per_page'])) {
653
                $filters['_per_page'] = (int) $filters['_per_page'];
654
            }
655
656
            // if filter persistence is configured
657
            if (null !== $this->filterPersister) {
658
                // if reset filters is asked, remove from storage
659
                if ('reset' === $this->request->query->get('filters')) {
660
                    $this->filterPersister->reset($this->getCode());
661
                }
662
663
                // if no filters, fetch from storage
664
                // otherwise save to storage
665
                if (empty($filters)) {
666
                    $filters = $this->filterPersister->get($this->getCode());
667
                } else {
668
                    $this->filterPersister->set($this->getCode(), $filters);
669
                }
670
            }
671
672
            $parameters = array_merge(
673
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
674
                $this->getDefaultSortValues(),
675
                $this->getDefaultFilterValues(),
676
                $filters
677
            );
678
679
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
680
                $parameters['_per_page'] = $this->getMaxPerPage();
681
            }
682
683
            // always force the parent value
684
            if ($this->isChild() && $this->getParentAssociationMapping()) {
685
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
686
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
687
            }
688
        }
689
690
        return $parameters;
691
    }
692
693
    /**
694
     * Returns the name of the parent related field, so the field can be use to set the default
695
     * value (ie the parent object) or to filter the object.
696
     *
697
     * @throws \InvalidArgumentException
698
     */
699
    public function getParentAssociationMapping(): ?string
700
    {
701
        if ($this->isChild()) {
702
            $parent = $this->getParent()->getCode();
703
704
            if (\array_key_exists($parent, $this->parentAssociationMapping)) {
705
                return $this->parentAssociationMapping[$parent];
706
            }
707
708
            throw new \InvalidArgumentException(sprintf(
709
                'There\'s no association between %s and %s.',
710
                $this->getCode(),
711
                $this->getParent()->getCode()
712
            ));
713
        }
714
715
        return null;
716
    }
717
718
    final public function addParentAssociationMapping(string $code, string $value): void
719
    {
720
        $this->parentAssociationMapping[$code] = $value;
721
    }
722
723
    /**
724
     * Returns the baseRoutePattern used to generate the routing information.
725
     *
726
     * @throws \RuntimeException
727
     *
728
     * @return string the baseRoutePattern used to generate the routing information
729
     */
730
    public function getBaseRoutePattern(): string
731
    {
732
        if (null !== $this->cachedBaseRoutePattern) {
733
            return $this->cachedBaseRoutePattern;
734
        }
735
736
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
737
            $baseRoutePattern = $this->baseRoutePattern;
738
            if (!$this->baseRoutePattern) {
739
                preg_match(self::CLASS_REGEX, $this->class, $matches);
740
741
                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...
742
                    throw new \RuntimeException(sprintf(
743
                        'Please define a default `baseRoutePattern` value for the admin class `%s`',
744
                        static::class
745
                    ));
746
                }
747
                $baseRoutePattern = $this->urlize($matches[5], '-');
748
            }
749
750
            $this->cachedBaseRoutePattern = sprintf(
751
                '%s/%s/%s',
752
                $this->getParent()->getBaseRoutePattern(),
753
                $this->getParent()->getRouterIdParameter(),
754
                $baseRoutePattern
755
            );
756
        } elseif ($this->baseRoutePattern) {
757
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
758
        } else {
759
            preg_match(self::CLASS_REGEX, $this->class, $matches);
760
761
            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...
762
                throw new \RuntimeException(sprintf(
763
                    'Please define a default `baseRoutePattern` value for the admin class `%s`',
764
                    static::class
765
                ));
766
            }
767
768
            $this->cachedBaseRoutePattern = sprintf(
769
                '/%s%s/%s',
770
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
771
                $this->urlize($matches[3], '-'),
772
                $this->urlize($matches[5], '-')
773
            );
774
        }
775
776
        return $this->cachedBaseRoutePattern;
777
    }
778
779
    /**
780
     * Returns the baseRouteName used to generate the routing information.
781
     *
782
     * @throws \RuntimeException
783
     *
784
     * @return string the baseRouteName used to generate the routing information
785
     */
786
    public function getBaseRouteName(): string
787
    {
788
        if (null !== $this->cachedBaseRouteName) {
789
            return $this->cachedBaseRouteName;
790
        }
791
792
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
793
            $baseRouteName = $this->baseRouteName;
794
            if (!$this->baseRouteName) {
795
                preg_match(self::CLASS_REGEX, $this->class, $matches);
796
797
                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...
798
                    throw new \RuntimeException(sprintf(
799
                        'Cannot automatically determine base route name,'
800
                        .' please define a default `baseRouteName` value for the admin class `%s`',
801
                        static::class
802
                    ));
803
                }
804
                $baseRouteName = $this->urlize($matches[5]);
805
            }
806
807
            $this->cachedBaseRouteName = sprintf(
808
                '%s_%s',
809
                $this->getParent()->getBaseRouteName(),
810
                $baseRouteName
811
            );
812
        } elseif ($this->baseRouteName) {
813
            $this->cachedBaseRouteName = $this->baseRouteName;
814
        } else {
815
            preg_match(self::CLASS_REGEX, $this->class, $matches);
816
817
            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...
818
                throw new \RuntimeException(sprintf(
819
                    'Cannot automatically determine base route name,'
820
                    .' please define a default `baseRouteName` value for the admin class `%s`',
821
                    static::class
822
                ));
823
            }
824
825
            $this->cachedBaseRouteName = sprintf(
826
                'admin_%s%s_%s',
827
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
828
                $this->urlize($matches[3]),
829
                $this->urlize($matches[5])
830
            );
831
        }
832
833
        return $this->cachedBaseRouteName;
834
    }
835
836
    public function getClass(): string
837
    {
838
        if ($this->hasActiveSubClass()) {
839
            if ($this->hasParentFieldDescription()) {
840
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
841
            }
842
843
            $subClass = $this->getRequest()->query->get('subclass');
844
845
            if (!$this->hasSubClass($subClass)) {
846
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
847
            }
848
849
            return $this->getSubClass($subClass);
850
        }
851
852
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
853
        if ($this->subject && \is_object($this->subject)) {
854
            return ClassUtils::getClass($this->subject);
855
        }
856
857
        return $this->class;
858
    }
859
860
    public function getSubClasses(): array
861
    {
862
        return $this->subClasses;
863
    }
864
865
    public function setSubClasses(array $subClasses): void
866
    {
867
        $this->subClasses = $subClasses;
868
    }
869
870
    public function hasSubClass(string $name): bool
871
    {
872
        return isset($this->subClasses[$name]);
873
    }
874
875
    public function hasActiveSubClass(): bool
876
    {
877
        if (\count($this->subClasses) > 0 && $this->request) {
878
            return null !== $this->getRequest()->query->get('subclass');
879
        }
880
881
        return false;
882
    }
883
884
    public function getActiveSubClass(): string
885
    {
886
        if (!$this->hasActiveSubClass()) {
887
            throw new \LogicException(sprintf(
888
                'Admin "%s" has no active subclass.',
889
                static::class
890
            ));
891
        }
892
893
        return $this->getSubClass($this->getActiveSubclassCode());
894
    }
895
896
    public function getActiveSubclassCode(): string
897
    {
898
        if (!$this->hasActiveSubClass()) {
899
            throw new \LogicException(sprintf(
900
                'Admin "%s" has no active subclass.',
901
                static::class
902
            ));
903
        }
904
905
        $subClass = $this->getRequest()->query->get('subclass');
906
907
        if (!$this->hasSubClass($subClass)) {
908
            throw new \LogicException(sprintf(
909
                'Admin "%s" has no active subclass.',
910
                static::class
911
            ));
912
        }
913
914
        return $subClass;
915
    }
916
917
    public function getBatchActions(): array
918
    {
919
        $actions = [];
920
921
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
922
            $actions['delete'] = [
923
                'label' => 'action_delete',
924
                'translation_domain' => 'SonataAdminBundle',
925
                'ask_confirmation' => true, // by default always true
926
            ];
927
        }
928
929
        $actions = $this->configureBatchActions($actions);
930
931
        foreach ($this->getExtensions() as $extension) {
932
            $actions = $extension->configureBatchActions($this, $actions);
933
        }
934
935
        foreach ($actions  as $name => &$action) {
936
            if (!\array_key_exists('label', $action)) {
937
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
938
            }
939
940
            if (!\array_key_exists('translation_domain', $action)) {
941
                $action['translation_domain'] = $this->getTranslationDomain();
942
            }
943
        }
944
945
        return $actions;
946
    }
947
948
    /**
949
     * NEXT_MAJOR: Create a `RouteCollectionInterface` and use as return type.
950
     */
951
    public function getRoutes(): RouteCollection
952
    {
953
        $this->buildRoutes();
954
955
        return $this->routes;
956
    }
957
958
    public function getRouterIdParameter(): string
959
    {
960
        return sprintf('{%s}', $this->getIdParameter());
961
    }
962
963
    public function getIdParameter(): string
964
    {
965
        $parameter = 'id';
966
967
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
968
            $parameter = sprintf('child%s', ucfirst($parameter));
969
        }
970
971
        return $parameter;
972
    }
973
974
    public function hasRoute(string $name): bool
975
    {
976
        if (!$this->routeGenerator) {
977
            throw new \RuntimeException('RouteGenerator cannot be null');
978
        }
979
980
        return $this->routeGenerator->hasAdminRoute($this, $name);
981
    }
982
983
    public function isCurrentRoute(string $name, ?string $adminCode = null): bool
984
    {
985
        if (!$this->hasRequest()) {
986
            return false;
987
        }
988
989
        $request = $this->getRequest();
990
        $route = $request->get('_route');
991
992
        if ($adminCode) {
993
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
994
        } else {
995
            $admin = $this;
996
        }
997
998
        if (!$admin) {
999
            return false;
1000
        }
1001
1002
        return sprintf('%s_%s', $admin->getBaseRouteName(), $name) === $route;
1003
    }
1004
1005
    public function generateObjectUrl(string $name, object $object, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1006
    {
1007
        $parameters['id'] = $this->getUrlSafeIdentifier($object);
1008
1009
        return $this->generateUrl($name, $parameters, $referenceType);
1010
    }
1011
1012
    public function generateUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1013
    {
1014
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $referenceType);
1015
    }
1016
1017
    public function generateMenuUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): array
1018
    {
1019
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $referenceType);
1020
    }
1021
1022
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry): void
1023
    {
1024
        $this->templateRegistry = $templateRegistry;
1025
    }
1026
1027
    /**
1028
     * @param array<string, string> $templates
1029
     */
1030
    public function setTemplates(array $templates): void
1031
    {
1032
        $this->getTemplateRegistry()->setTemplates($templates);
1033
    }
1034
1035
    /**
1036
     * {@inheritdoc}
1037
     */
1038
    public function setTemplate(string $name, string $template): void
1039
    {
1040
        $this->getTemplateRegistry()->setTemplate($name, $template);
1041
    }
1042
1043
    public function getNewInstance(): object
1044
    {
1045
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1046
1047
        $this->appendParentObject($object);
1048
1049
        foreach ($this->getExtensions() as $extension) {
1050
            $extension->alterNewInstance($this, $object);
1051
        }
1052
1053
        return $object;
1054
    }
1055
1056
    public function getFormBuilder(): FormBuilderInterface
1057
    {
1058
        $this->formOptions['data_class'] = $this->getClass();
1059
1060
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1061
            $this->getUniqid(),
1062
            $this->formOptions
1063
        );
1064
1065
        $this->defineFormBuilder($formBuilder);
1066
1067
        return $formBuilder;
1068
    }
1069
1070
    /**
1071
     * This method is being called by the main admin class and the child class,
1072
     * the getFormBuilder is only call by the main admin class.
1073
     */
1074
    public function defineFormBuilder(FormBuilderInterface $formBuilder): void
1075
    {
1076
        if (!$this->hasSubject()) {
1077
            throw new \LogicException(sprintf(
1078
                'Admin "%s" has no subject.',
1079
                static::class
1080
            ));
1081
        }
1082
1083
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1084
1085
        $this->configureFormFields($mapper);
1086
1087
        foreach ($this->getExtensions() as $extension) {
1088
            $extension->configureFormFields($mapper);
1089
        }
1090
1091
        $this->attachInlineValidator();
1092
    }
1093
1094
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription): void
1095
    {
1096
        $pool = $this->getConfigurationPool();
1097
1098
        $adminCode = $fieldDescription->getOption('admin_code');
1099
1100
        if (null !== $adminCode) {
1101
            if (!$pool->hasAdminByAdminCode($adminCode)) {
1102
                return;
1103
            }
1104
1105
            $admin = $pool->getAdminByAdminCode($adminCode);
1106
        } else {
1107
            $targetModel = $fieldDescription->getTargetModel();
1108
1109
            if (!$pool->hasAdminByClass($targetModel)) {
1110
                return;
1111
            }
1112
1113
            $admin = $pool->getAdminByClass($targetModel);
1114
        }
1115
1116
        if ($this->hasRequest()) {
1117
            $admin->setRequest($this->getRequest());
1118
        }
1119
1120
        $fieldDescription->setAssociationAdmin($admin);
1121
    }
1122
1123
    public function getObject($id): ?object
1124
    {
1125
        $object = $this->getModelManager()->find($this->getClass(), $id);
1126
        foreach ($this->getExtensions() as $extension) {
1127
            $extension->alterObject($this, $object);
0 ignored issues
show
Documentation introduced by
$object is of type object|null, but the function expects a object<Sonata\AdminBundle\Admin\object>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1128
        }
1129
1130
        return $object;
1131
    }
1132
1133
    public function getForm(): ?FormInterface
1134
    {
1135
        $this->buildForm();
1136
1137
        return $this->form;
1138
    }
1139
1140
    public function getList(): ?FieldDescriptionCollection
1141
    {
1142
        $this->buildList();
1143
1144
        return $this->list;
1145
    }
1146
1147
    /**
1148
     * @final since sonata-project/admin-bundle 3.63.0
1149
     */
1150
    public function createQuery($context = 'list'): ProxyQueryInterface
1151
    {
1152
        if (\func_num_args() > 0) {
1153
            @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...
1154
                'The $context argument of %s is deprecated since 3.3, to be removed in 4.0.',
1155
                __METHOD__
1156
            ), E_USER_DEPRECATED);
1157
        }
1158
1159
        $query = $this->getModelManager()->createQuery($this->getClass());
1160
1161
        $query = $this->configureQuery($query);
1162
        foreach ($this->extensions as $extension) {
1163
            $extension->configureQuery($this, $query, $context);
1164
        }
1165
1166
        return $query;
1167
    }
1168
1169
    public function getDatagrid(): DatagridInterface
1170
    {
1171
        $this->buildDatagrid();
1172
1173
        return $this->datagrid;
1174
    }
1175
1176
    public function buildTabMenu(string $action, ?AdminInterface $childAdmin = null): ItemInterface
1177
    {
1178
        if ($this->loaded['tab_menu']) {
1179
            return $this->menu;
1180
        }
1181
1182
        $this->loaded['tab_menu'] = true;
1183
1184
        $menu = $this->menuFactory->createItem('root');
1185
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1186
        $menu->setExtra('translation_domain', $this->translationDomain);
1187
1188
        // Prevents BC break with KnpMenuBundle v1.x
1189
        if (method_exists($menu, 'setCurrentUri')) {
1190
            $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...
1191
        }
1192
1193
        $this->configureTabMenu($menu, $action, $childAdmin);
1194
1195
        foreach ($this->getExtensions() as $extension) {
1196
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1197
        }
1198
1199
        $this->menu = $menu;
1200
1201
        return $this->menu;
1202
    }
1203
1204
    public function getSideMenu(string $action, ?AdminInterface $childAdmin = null): ItemInterface
1205
    {
1206
        if ($this->isChild()) {
1207
            return $this->getParent()->getSideMenu($action, $this);
1208
        }
1209
1210
        $this->buildTabMenu($action, $childAdmin);
1211
1212
        return $this->menu;
1213
    }
1214
1215
    public function getRootCode(): string
1216
    {
1217
        return $this->getRoot()->getCode();
1218
    }
1219
1220
    public function getRoot(): AdminInterface
1221
    {
1222
        if (!$this->hasParentFieldDescription()) {
1223
            return $this;
1224
        }
1225
1226
        return $this->getParentFieldDescription()->getAdmin()->getRoot();
1227
    }
1228
1229
    public function setBaseControllerName(string $baseControllerName): void
1230
    {
1231
        $this->baseControllerName = $baseControllerName;
1232
    }
1233
1234
    public function getBaseControllerName(): string
1235
    {
1236
        return $this->baseControllerName;
1237
    }
1238
1239
    public function setLabel(?string $label): void
1240
    {
1241
        $this->label = $label;
1242
    }
1243
1244
    public function getLabel(): ?string
1245
    {
1246
        return $this->label;
1247
    }
1248
1249
    public function setFilterPersister(?FilterPersisterInterface $filterPersister = null): void
1250
    {
1251
        $this->filterPersister = $filterPersister;
1252
    }
1253
1254
    public function getMaxPerPage(): int
1255
    {
1256
        $sortValues = $this->getModelManager()->getDefaultSortValues($this->class);
1257
1258
        return $sortValues['_per_page'] ?? 25;
1259
    }
1260
1261
    public function setMaxPageLinks(int $maxPageLinks): void
1262
    {
1263
        $this->maxPageLinks = $maxPageLinks;
1264
    }
1265
1266
    public function getMaxPageLinks(): int
1267
    {
1268
        return $this->maxPageLinks;
1269
    }
1270
1271
    public function getFormGroups(): array
1272
    {
1273
        return $this->formGroups;
1274
    }
1275
1276
    public function setFormGroups(array $formGroups): void
1277
    {
1278
        $this->formGroups = $formGroups;
1279
    }
1280
1281
    public function removeFieldFromFormGroup(string $key): void
1282
    {
1283
        foreach ($this->formGroups as $name => $formGroup) {
1284
            unset($this->formGroups[$name]['fields'][$key]);
1285
1286
            if (empty($this->formGroups[$name]['fields'])) {
1287
                unset($this->formGroups[$name]);
1288
            }
1289
        }
1290
    }
1291
1292
    public function reorderFormGroup(string $group, array $keys): void
1293
    {
1294
        $formGroups = $this->getFormGroups();
1295
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1296
        $this->setFormGroups($formGroups);
1297
    }
1298
1299
    public function getFormTabs(): array
1300
    {
1301
        return $this->formTabs;
1302
    }
1303
1304
    public function setFormTabs(array $formTabs): void
1305
    {
1306
        $this->formTabs = $formTabs;
1307
    }
1308
1309
    public function getShowTabs(): array
1310
    {
1311
        return $this->showTabs;
1312
    }
1313
1314
    public function setShowTabs(array $showTabs): void
1315
    {
1316
        $this->showTabs = $showTabs;
1317
    }
1318
1319
    public function getShowGroups(): array
1320
    {
1321
        return $this->showGroups;
1322
    }
1323
1324
    public function setShowGroups(array $showGroups): void
1325
    {
1326
        $this->showGroups = $showGroups;
1327
    }
1328
1329
    public function reorderShowGroup(string $group, array $keys): void
1330
    {
1331
        $showGroups = $this->getShowGroups();
1332
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1333
        $this->setShowGroups($showGroups);
1334
    }
1335
1336
    public function setParentFieldDescription(?FieldDescriptionInterface $parentFieldDescription): void
1337
    {
1338
        $this->parentFieldDescription = $parentFieldDescription;
1339
    }
1340
1341
    public function getParentFieldDescription(): ?FieldDescriptionInterface
1342
    {
1343
        if (!$this->hasParentFieldDescription()) {
1344
            throw new \LogicException(sprintf(
1345
                'Admin "%s" has no parent field description.',
1346
                static::class
1347
            ));
1348
        }
1349
1350
        return $this->parentFieldDescription;
1351
    }
1352
1353
    public function hasParentFieldDescription(): bool
1354
    {
1355
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1356
    }
1357
1358
    public function setSubject(?object $subject): void
1359
    {
1360
        if (\is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1361
            $message = <<<'EOT'
1362
You are trying to set entity an instance of "%s",
1363
which is not the one registered with this admin class ("%s").
1364
This is deprecated since 3.5 and will no longer be supported in 4.0.
1365
EOT;
1366
1367
            // NEXT_MAJOR : throw an exception instead
1368
            @trigger_error(sprintf($message, \get_class($subject), $this->getClass()), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1369
        }
1370
1371
        $this->subject = $subject;
1372
    }
1373
1374
    public function getSubject(): ?object
1375
    {
1376
        if (!$this->hasSubject()) {
1377
            throw new \LogicException(sprintf(
1378
                'Admin "%s" has no subject.',
1379
                static::class
1380
            ));
1381
        }
1382
1383
        return $this->subject;
1384
    }
1385
1386
    public function hasSubject(): bool
1387
    {
1388
        if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
1389
            $id = $this->request->get($this->getIdParameter());
1390
1391
            if (null !== $id) {
1392
                $this->subject = $this->getObject($id);
1393
            }
1394
        }
1395
1396
        return null !== $this->subject;
1397
    }
1398
1399
    public function getFormFieldDescriptions(): array
1400
    {
1401
        $this->buildForm();
1402
1403
        return $this->formFieldDescriptions;
1404
    }
1405
1406
    public function getFormFieldDescription(string $name): ?FieldDescriptionInterface
1407
    {
1408
        $this->buildForm();
1409
1410
        if (!$this->hasFormFieldDescription($name)) {
1411
            throw new \LogicException(sprintf(
1412
                'Admin "%s" has no form field description for the field %s.',
1413
                static::class,
1414
                $name
1415
            ));
1416
        }
1417
1418
        return $this->formFieldDescriptions[$name];
1419
    }
1420
1421
    /**
1422
     * Returns true if the admin has a FieldDescription with the given $name.
1423
     */
1424
    public function hasFormFieldDescription(string $name): bool
1425
    {
1426
        $this->buildForm();
1427
1428
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1429
    }
1430
1431
    public function addFormFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1432
    {
1433
        $this->formFieldDescriptions[$name] = $fieldDescription;
1434
    }
1435
1436
    /**
1437
     * remove a FieldDescription.
1438
     */
1439
    public function removeFormFieldDescription(string $name): void
1440
    {
1441
        unset($this->formFieldDescriptions[$name]);
1442
    }
1443
1444
    /**
1445
     * build and return the collection of form FieldDescription.
1446
     *
1447
     * @return FieldDescriptionInterface[] collection of form FieldDescription
1448
     */
1449
    public function getShowFieldDescriptions(): array
1450
    {
1451
        $this->buildShow();
1452
1453
        return $this->showFieldDescriptions;
1454
    }
1455
1456
    /**
1457
     * Returns the form FieldDescription with the given $name.
1458
     */
1459
    public function getShowFieldDescription(string $name): FieldDescriptionInterface
1460
    {
1461
        $this->buildShow();
1462
1463
        if (!$this->hasShowFieldDescription($name)) {
1464
            throw new \LogicException(sprintf(
1465
                'Admin "%s" has no show field description for the field %s.',
1466
                static::class,
1467
                $name
1468
            ));
1469
        }
1470
1471
        return $this->showFieldDescriptions[$name];
1472
    }
1473
1474
    public function hasShowFieldDescription(string $name): bool
1475
    {
1476
        $this->buildShow();
1477
1478
        return \array_key_exists($name, $this->showFieldDescriptions);
1479
    }
1480
1481
    public function addShowFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1482
    {
1483
        $this->showFieldDescriptions[$name] = $fieldDescription;
1484
    }
1485
1486
    public function removeShowFieldDescription(string $name): void
1487
    {
1488
        unset($this->showFieldDescriptions[$name]);
1489
    }
1490
1491
    public function getListFieldDescriptions(): array
1492
    {
1493
        $this->buildList();
1494
1495
        return $this->listFieldDescriptions;
1496
    }
1497
1498
    public function getListFieldDescription(string $name): FieldDescriptionInterface
1499
    {
1500
        $this->buildList();
1501
1502
        if (!$this->hasListFieldDescription($name)) {
1503
            throw new \LogicException(sprintf(
1504
                'Admin "%s" has no list field description for %s.',
1505
                static::class,
1506
                $name
1507
            ));
1508
        }
1509
1510
        return $this->listFieldDescriptions[$name];
1511
    }
1512
1513
    public function hasListFieldDescription(string $name): bool
1514
    {
1515
        $this->buildList();
1516
1517
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1518
    }
1519
1520
    public function addListFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1521
    {
1522
        $this->listFieldDescriptions[$name] = $fieldDescription;
1523
    }
1524
1525
    public function removeListFieldDescription(string $name): void
1526
    {
1527
        unset($this->listFieldDescriptions[$name]);
1528
    }
1529
1530
    public function getFilterFieldDescription(string $name): FieldDescriptionInterface
1531
    {
1532
        $this->buildDatagrid();
1533
1534
        if (!$this->hasFilterFieldDescription($name)) {
1535
            throw new \LogicException(sprintf(
1536
                'Admin "%s" has no filter field description for the field %s.',
1537
                static::class,
1538
                $name
1539
            ));
1540
        }
1541
1542
        return $this->filterFieldDescriptions[$name];
1543
    }
1544
1545
    public function hasFilterFieldDescription(string $name): bool
1546
    {
1547
        $this->buildDatagrid();
1548
1549
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1550
    }
1551
1552
    public function addFilterFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1553
    {
1554
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1555
    }
1556
1557
    public function removeFilterFieldDescription(string $name): void
1558
    {
1559
        unset($this->filterFieldDescriptions[$name]);
1560
    }
1561
1562
    public function getFilterFieldDescriptions(): array
1563
    {
1564
        $this->buildDatagrid();
1565
1566
        return $this->filterFieldDescriptions;
1567
    }
1568
1569
    public function addChild(AdminInterface $child, string $field): void
1570
    {
1571
        $parentAdmin = $this;
1572
        while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
1573
            $parentAdmin = $parentAdmin->getParent();
1574
        }
1575
1576
        if ($parentAdmin->getCode() === $child->getCode()) {
1577
            throw new \RuntimeException(sprintf(
1578
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1579
                $child->getCode(),
1580
                $this->getCode()
1581
            ));
1582
        }
1583
1584
        $this->children[$child->getCode()] = $child;
1585
1586
        $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...
1587
        $child->addParentAssociationMapping($this->getCode(), $field);
1588
    }
1589
1590
    public function hasChild(string $code): bool
1591
    {
1592
        return isset($this->children[$code]);
1593
    }
1594
1595
    public function getChildren(): array
1596
    {
1597
        return $this->children;
1598
    }
1599
1600
    public function getChild(string $code): AdminInterface
1601
    {
1602
        if (!$this->hasChild($code)) {
1603
            throw new \LogicException(sprintf(
1604
                'Admin "%s" has no child for the code %s.',
1605
                static::class,
1606
                $code
1607
            ));
1608
        }
1609
1610
        return $this->children[$code];
1611
    }
1612
1613
    public function setParent(AdminInterface $parent): void
1614
    {
1615
        $this->parent = $parent;
1616
    }
1617
1618
    public function getParent(): AdminInterface
1619
    {
1620
        if (!$this->isChild()) {
1621
            throw new \LogicException(sprintf(
1622
                'Admin "%s" has no parent.',
1623
                static::class
1624
            ));
1625
        }
1626
1627
        return $this->parent;
1628
    }
1629
1630
    final public function getRootAncestor(): AdminInterface
1631
    {
1632
        $parent = $this;
1633
1634
        while ($parent->isChild()) {
1635
            $parent = $parent->getParent();
1636
        }
1637
1638
        return $parent;
1639
    }
1640
1641
    final public function getChildDepth(): int
1642
    {
1643
        $parent = $this;
1644
        $depth = 0;
1645
1646
        while ($parent->isChild()) {
1647
            $parent = $parent->getParent();
1648
            ++$depth;
1649
        }
1650
1651
        return $depth;
1652
    }
1653
1654
    final public function getCurrentLeafChildAdmin(): ?AdminInterface
1655
    {
1656
        $child = $this->getCurrentChildAdmin();
1657
1658
        if (null === $child) {
1659
            return null;
1660
        }
1661
1662
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1663
            $child = $c;
1664
        }
1665
1666
        return $child;
1667
    }
1668
1669
    public function isChild(): bool
1670
    {
1671
        return $this->parent instanceof AdminInterface;
1672
    }
1673
1674
    /**
1675
     * Returns true if the admin has children, false otherwise.
1676
     */
1677
    public function hasChildren(): bool
1678
    {
1679
        return \count($this->children) > 0;
1680
    }
1681
1682
    public function setUniqid(string $uniqid): void
1683
    {
1684
        $this->uniqid = $uniqid;
1685
    }
1686
1687
    public function getUniqid(): string
1688
    {
1689
        if (!$this->uniqid) {
1690
            $this->uniqid = sprintf('s%s', uniqid());
1691
        }
1692
1693
        return $this->uniqid;
1694
    }
1695
1696
    /**
1697
     * {@inheritdoc}
1698
     */
1699
    public function getClassnameLabel(): string
1700
    {
1701
        return $this->classnameLabel;
1702
    }
1703
1704
    public function getPersistentParameters(): array
1705
    {
1706
        $parameters = [];
1707
1708
        foreach ($this->getExtensions() as $extension) {
1709
            $params = $extension->getPersistentParameters($this);
1710
1711
            $parameters = array_merge($parameters, $params);
1712
        }
1713
1714
        return $parameters;
1715
    }
1716
1717
    /**
1718
     * {@inheritdoc}
1719
     */
1720
    public function getPersistentParameter(string $name)
1721
    {
1722
        $parameters = $this->getPersistentParameters();
1723
1724
        return $parameters[$name] ?? null;
1725
    }
1726
1727
    public function setCurrentChild(bool $currentChild): void
1728
    {
1729
        $this->currentChild = $currentChild;
1730
    }
1731
1732
    public function isCurrentChild(): bool
1733
    {
1734
        return $this->currentChild;
1735
    }
1736
1737
    /**
1738
     * Returns the current child admin instance.
1739
     *
1740
     * @return AdminInterface|null the current child admin instance
1741
     */
1742
    public function getCurrentChildAdmin(): ?AdminInterface
1743
    {
1744
        foreach ($this->children as $children) {
1745
            if ($children->isCurrentChild()) {
1746
                return $children;
1747
            }
1748
        }
1749
1750
        return null;
1751
    }
1752
1753
    public function setTranslationDomain(string $translationDomain): void
1754
    {
1755
        $this->translationDomain = $translationDomain;
1756
    }
1757
1758
    public function getTranslationDomain(): string
1759
    {
1760
        return $this->translationDomain;
1761
    }
1762
1763
    public function getTranslationLabel(string $label, string $context = '', string $type = ''): string
1764
    {
1765
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
1766
    }
1767
1768
    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...
1769
    {
1770
        $this->request = $request;
1771
1772
        foreach ($this->getChildren() as $children) {
1773
            $children->setRequest($request);
1774
        }
1775
    }
1776
1777
    public function getRequest(): Request
1778
    {
1779
        if (!$this->request) {
1780
            throw new \LogicException('The Request object has not been set');
1781
        }
1782
1783
        return $this->request;
1784
    }
1785
1786
    public function hasRequest(): bool
1787
    {
1788
        return null !== $this->request;
1789
    }
1790
1791
    public function setFormContractor(?FormContractorInterface $formBuilder): void
1792
    {
1793
        $this->formContractor = $formBuilder;
1794
    }
1795
1796
    public function getFormContractor(): ?FormContractorInterface
1797
    {
1798
        return $this->formContractor;
1799
    }
1800
1801
    public function setDatagridBuilder(?DatagridBuilderInterface $datagridBuilder): void
1802
    {
1803
        $this->datagridBuilder = $datagridBuilder;
1804
    }
1805
1806
    public function getDatagridBuilder(): ?DatagridBuilderInterface
1807
    {
1808
        return $this->datagridBuilder;
1809
    }
1810
1811
    public function setListBuilder(?ListBuilderInterface $listBuilder): void
1812
    {
1813
        $this->listBuilder = $listBuilder;
1814
    }
1815
1816
    public function getListBuilder(): ?ListBuilderInterface
1817
    {
1818
        return $this->listBuilder;
1819
    }
1820
1821
    public function setShowBuilder(?ShowBuilderInterface $showBuilder): void
1822
    {
1823
        $this->showBuilder = $showBuilder;
1824
    }
1825
1826
    public function getShowBuilder(): ?ShowBuilderInterface
1827
    {
1828
        return $this->showBuilder;
1829
    }
1830
1831
    public function setConfigurationPool(?Pool $configurationPool): void
1832
    {
1833
        $this->configurationPool = $configurationPool;
1834
    }
1835
1836
    public function getConfigurationPool(): ?Pool
1837
    {
1838
        return $this->configurationPool;
1839
    }
1840
1841
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator): void
1842
    {
1843
        $this->routeGenerator = $routeGenerator;
1844
    }
1845
1846
    public function getRouteGenerator(): ?RouteGeneratorInterface
1847
    {
1848
        return $this->routeGenerator;
1849
    }
1850
1851
    public function getCode(): string
1852
    {
1853
        return $this->code;
1854
    }
1855
1856
    public function getBaseCodeRoute(): string
1857
    {
1858
        if ($this->isChild()) {
1859
            return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
1860
        }
1861
1862
        return $this->getCode();
1863
    }
1864
1865
    public function getModelManager(): ?ModelManagerInterface
1866
    {
1867
        return $this->modelManager;
1868
    }
1869
1870
    public function setModelManager(?ModelManagerInterface $modelManager): void
1871
    {
1872
        $this->modelManager = $modelManager;
1873
    }
1874
1875
    public function getManagerType(): ?string
1876
    {
1877
        return $this->managerType;
1878
    }
1879
1880
    public function setManagerType(?string $type): void
1881
    {
1882
        $this->managerType = $type;
1883
    }
1884
1885
    public function getObjectIdentifier()
1886
    {
1887
        return $this->getCode();
1888
    }
1889
1890
    /**
1891
     * Set the roles and permissions per role.
1892
     */
1893
    public function setSecurityInformation(array $information): void
1894
    {
1895
        $this->securityInformation = $information;
1896
    }
1897
1898
    public function getSecurityInformation(): array
1899
    {
1900
        return $this->securityInformation;
1901
    }
1902
1903
    /**
1904
     * Return the list of permissions the user should have in order to display the admin.
1905
     */
1906
    public function getPermissionsShow(string $context): array
1907
    {
1908
        switch ($context) {
1909
            case self::CONTEXT_DASHBOARD:
1910
            case self::CONTEXT_MENU:
1911
            default:
1912
                return ['LIST'];
1913
        }
1914
    }
1915
1916
    public function showIn(string $context): bool
1917
    {
1918
        switch ($context) {
1919
            case self::CONTEXT_DASHBOARD:
1920
            case self::CONTEXT_MENU:
1921
            default:
1922
                return $this->isGranted($this->getPermissionsShow($context));
0 ignored issues
show
Documentation introduced by
$this->getPermissionsShow($context) is of type array, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1923
        }
1924
    }
1925
1926
    public function createObjectSecurity(object $object): void
1927
    {
1928
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
1929
    }
1930
1931
    public function setSecurityHandler(?SecurityHandlerInterface $securityHandler): void
1932
    {
1933
        $this->securityHandler = $securityHandler;
1934
    }
1935
1936
    public function getSecurityHandler(): ?SecurityHandlerInterface
1937
    {
1938
        return $this->securityHandler;
1939
    }
1940
1941
    /**
1942
     * NEXT_MAJOR: Decide the type declaration for the $name argument, since it is
1943
     * passed as argument 1 for `SecurityHandlerInterface::isGranted()`, which
1944
     * accepts string and array.
1945
     */
1946
    public function isGranted($name, ?object $object = null): bool
1947
    {
1948
        $objectRef = $object ? sprintf('/%s#%s', spl_object_hash($object), $this->id($object)) : '';
1949
        $key = md5(json_encode($name).$objectRef);
1950
1951
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
1952
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
1953
        }
1954
1955
        return $this->cacheIsGranted[$key];
1956
    }
1957
1958
    /**
1959
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
1960
     * passed as argument 1 for `ModelManagerInterface::getUrlSafeIdentifier()`, which
1961
     * accepts null.
1962
     */
1963
    public function getUrlSafeIdentifier($model): ?string
1964
    {
1965
        return $this->getModelManager()->getUrlSafeIdentifier($model);
1966
    }
1967
1968
    /**
1969
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
1970
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
1971
     * accepts null.
1972
     */
1973
    public function getNormalizedIdentifier($model): ?string
1974
    {
1975
        return $this->getModelManager()->getNormalizedIdentifier($model);
1976
    }
1977
1978
    /**
1979
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
1980
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
1981
     * accepts null.
1982
     */
1983
    public function id($model): ?string
1984
    {
1985
        return $this->getNormalizedIdentifier($model);
1986
    }
1987
1988
    public function setValidator(?ValidatorInterface $validator): void
1989
    {
1990
        $this->validator = $validator;
1991
    }
1992
1993
    public function getValidator(): ?ValidatorInterface
1994
    {
1995
        return $this->validator;
1996
    }
1997
1998
    public function getShow(): ?FieldDescriptionCollection
1999
    {
2000
        $this->buildShow();
2001
2002
        return $this->show;
2003
    }
2004
2005
    public function setFormTheme(array $formTheme): void
2006
    {
2007
        $this->formTheme = $formTheme;
2008
    }
2009
2010
    public function getFormTheme(): array
2011
    {
2012
        return $this->formTheme;
2013
    }
2014
2015
    public function setFilterTheme(array $filterTheme): void
2016
    {
2017
        $this->filterTheme = $filterTheme;
2018
    }
2019
2020
    public function getFilterTheme(): array
2021
    {
2022
        return $this->filterTheme;
2023
    }
2024
2025
    public function addExtension(AdminExtensionInterface $extension): void
2026
    {
2027
        $this->extensions[] = $extension;
2028
    }
2029
2030
    public function getExtensions(): array
2031
    {
2032
        return $this->extensions;
2033
    }
2034
2035
    public function setMenuFactory(?FactoryInterface $menuFactory): void
2036
    {
2037
        $this->menuFactory = $menuFactory;
2038
    }
2039
2040
    public function getMenuFactory(): ?FactoryInterface
2041
    {
2042
        return $this->menuFactory;
2043
    }
2044
2045
    public function setRouteBuilder(?RouteBuilderInterface $routeBuilder): void
2046
    {
2047
        $this->routeBuilder = $routeBuilder;
2048
    }
2049
2050
    public function getRouteBuilder(): ?RouteBuilderInterface
2051
    {
2052
        return $this->routeBuilder;
2053
    }
2054
2055
    /**
2056
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since there
2057
     * are tests ensuring to accept null (`GetShortObjectDescriptionActionTest::testGetShortObjectDescriptionActionEmptyObjectIdAsJson()`).
2058
     */
2059
    public function toString($object): string
2060
    {
2061
        if (!\is_object($object)) {
2062
            return '';
2063
        }
2064
2065
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2066
            return (string) $object;
2067
        }
2068
2069
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2070
    }
2071
2072
    public function setLabelTranslatorStrategy(?LabelTranslatorStrategyInterface $labelTranslatorStrategy): void
2073
    {
2074
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2075
    }
2076
2077
    public function getLabelTranslatorStrategy(): ?LabelTranslatorStrategyInterface
2078
    {
2079
        return $this->labelTranslatorStrategy;
2080
    }
2081
2082
    public function supportsPreviewMode(): bool
2083
    {
2084
        return $this->supportsPreviewMode;
2085
    }
2086
2087
    /**
2088
     * Returns predefined per page options.
2089
     */
2090
    public function getPerPageOptions(): array
2091
    {
2092
        $perPageOptions = $this->getModelManager()->getDefaultPerPageOptions($this->class);
2093
        $perPageOptions[] = $this->getMaxPerPage();
2094
2095
        $perPageOptions = array_unique($perPageOptions);
2096
        sort($perPageOptions);
2097
2098
        return $perPageOptions;
2099
    }
2100
2101
    /**
2102
     * Set pager type.
2103
     */
2104
    public function setPagerType(string $pagerType): void
2105
    {
2106
        $this->pagerType = $pagerType;
2107
    }
2108
2109
    /**
2110
     * Get pager type.
2111
     */
2112
    public function getPagerType(): string
2113
    {
2114
        return $this->pagerType;
2115
    }
2116
2117
    /**
2118
     * Returns true if the per page value is allowed, false otherwise.
2119
     */
2120
    public function determinedPerPageValue(int $perPage): bool
2121
    {
2122
        return \in_array($perPage, $this->getPerPageOptions(), true);
2123
    }
2124
2125
    public function isAclEnabled(): bool
2126
    {
2127
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2128
    }
2129
2130
    /**
2131
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since it is
2132
     * passed as argument 1 to `toString()` method, which currently accepts null.
2133
     */
2134
    public function getObjectMetadata($object): MetadataInterface
2135
    {
2136
        return new Metadata($this->toString($object));
2137
    }
2138
2139
    public function getListModes(): array
2140
    {
2141
        return $this->listModes;
2142
    }
2143
2144
    public function setListMode(string $mode): void
2145
    {
2146
        if (!$this->hasRequest()) {
2147
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2148
        }
2149
2150
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2151
    }
2152
2153
    public function getListMode(): string
2154
    {
2155
        if (!$this->hasRequest()) {
2156
            return 'list';
2157
        }
2158
2159
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2160
    }
2161
2162
    public function getAccessMapping(): array
2163
    {
2164
        return $this->accessMapping;
2165
    }
2166
2167
    public function checkAccess(string $action, ?object $object = null): void
2168
    {
2169
        $access = $this->getAccess();
2170
2171
        if (!\array_key_exists($action, $access)) {
2172
            throw new \InvalidArgumentException(sprintf(
2173
                'Action "%s" could not be found in access mapping.'
2174
                .' Please make sure your action is defined into your admin class accessMapping property.',
2175
                $action
2176
            ));
2177
        }
2178
2179
        if (!\is_array($access[$action])) {
2180
            $access[$action] = [$access[$action]];
2181
        }
2182
2183
        foreach ($access[$action] as $role) {
2184
            if (false === $this->isGranted($role, $object)) {
2185
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2186
            }
2187
        }
2188
    }
2189
2190
    /**
2191
     * {@inheritdoc}
2192
     */
2193
    public function hasAccess(string $action, ?object $object = null): bool
2194
    {
2195
        $access = $this->getAccess();
2196
2197
        if (!\array_key_exists($action, $access)) {
2198
            return false;
2199
        }
2200
2201
        if (!\is_array($access[$action])) {
2202
            $access[$action] = [$access[$action]];
2203
        }
2204
2205
        foreach ($access[$action] as $role) {
2206
            if (false === $this->isGranted($role, $object)) {
2207
                return false;
2208
            }
2209
        }
2210
2211
        return true;
2212
    }
2213
2214
    final public function getActionButtons(string $action, ?object $object = null): array
2215
    {
2216
        $buttonList = [];
2217
2218
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
2219
            && $this->hasAccess('create')
2220
            && $this->hasRoute('create')
2221
        ) {
2222
            $buttonList['create'] = [
2223
                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2224
            ];
2225
        }
2226
2227
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
2228
            && $this->canAccessObject('edit', $object)
2229
            && $this->hasRoute('edit')
2230
        ) {
2231
            $buttonList['edit'] = [
2232
                'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2233
            ];
2234
        }
2235
2236
        if (\in_array($action, ['show', 'edit', 'acl'], true)
2237
            && $this->canAccessObject('history', $object)
2238
            && $this->hasRoute('history')
2239
        ) {
2240
            $buttonList['history'] = [
2241
                'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2242
            ];
2243
        }
2244
2245
        if (\in_array($action, ['edit', 'history'], true)
2246
            && $this->isAclEnabled()
2247
            && $this->canAccessObject('acl', $object)
2248
            && $this->hasRoute('acl')
2249
        ) {
2250
            $buttonList['acl'] = [
2251
                'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2252
            ];
2253
        }
2254
2255
        if (\in_array($action, ['edit', 'history', 'acl'], true)
2256
            && $this->canAccessObject('show', $object)
2257
            && \count($this->getShow()) > 0
2258
            && $this->hasRoute('show')
2259
        ) {
2260
            $buttonList['show'] = [
2261
                'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2262
            ];
2263
        }
2264
2265
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
2266
            && $this->hasAccess('list')
2267
            && $this->hasRoute('list')
2268
        ) {
2269
            $buttonList['list'] = [
2270
                'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2271
            ];
2272
        }
2273
2274
        $buttonList = $this->configureActionButtons($buttonList, $action, $object);
2275
2276
        foreach ($this->getExtensions() as $extension) {
2277
            $buttonList = $extension->configureActionButtons($this, $buttonList, $action, $object);
2278
        }
2279
2280
        return $buttonList;
2281
    }
2282
2283
    /**
2284
     * {@inheritdoc}
2285
     */
2286
    public function getDashboardActions(): array
2287
    {
2288
        $actions = [];
2289
2290
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2291
            $actions['create'] = [
2292
                'label' => 'link_add',
2293
                'translation_domain' => 'SonataAdminBundle',
2294
                'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2295
                'url' => $this->generateUrl('create'),
2296
                'icon' => 'plus-circle',
2297
            ];
2298
        }
2299
2300
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2301
            $actions['list'] = [
2302
                'label' => 'link_list',
2303
                'translation_domain' => 'SonataAdminBundle',
2304
                'url' => $this->generateUrl('list'),
2305
                'icon' => 'list',
2306
            ];
2307
        }
2308
2309
        return $actions;
2310
    }
2311
2312
    /**
2313
     * {@inheritdoc}
2314
     */
2315
    final public function showMosaicButton($isShown): void
2316
    {
2317
        if ($isShown) {
2318
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
2319
        } else {
2320
            unset($this->listModes['mosaic']);
2321
        }
2322
    }
2323
2324
    final public function getSearchResultLink(object $object): ?string
2325
    {
2326
        foreach ($this->searchResultActions as $action) {
2327
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2328
                return $this->generateObjectUrl($action, $object);
2329
            }
2330
        }
2331
2332
        return null;
2333
    }
2334
2335
    /**
2336
     * Checks if a filter type is set to a default value.
2337
     */
2338
    final public function isDefaultFilter(string $name): bool
2339
    {
2340
        $filter = $this->getFilterParameters();
2341
        $default = $this->getDefaultFilterValues();
2342
2343
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
2344
            return false;
2345
        }
2346
2347
        return $filter[$name] === $default[$name];
2348
    }
2349
2350
    public function canAccessObject(string $action, ?object $object = null): bool
2351
    {
2352
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2353
    }
2354
2355
    public function configureActionButtons(array $buttonList, string $action, ?object $object = null): array
2356
    {
2357
        return $buttonList;
2358
    }
2359
2360
    /**
2361
     * Hook to run after initialization.
2362
     */
2363
    protected function configure(): void
2364
    {
2365
    }
2366
2367
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
2368
    {
2369
        return $query;
2370
    }
2371
2372
    /**
2373
     * urlize the given word.
2374
     *
2375
     * @param string $sep the separator
2376
     */
2377
    final protected function urlize(string $word, string $sep = '_'): string
2378
    {
2379
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
2380
    }
2381
2382
    final protected function getTemplateRegistry(): MutableTemplateRegistryInterface
2383
    {
2384
        return $this->templateRegistry;
2385
    }
2386
2387
    /**
2388
     * Returns a list of default sort values.
2389
     *
2390
     * @return array{_page?: int, _per_page?: int, _sort_by?: string, _sort_order?: string}
0 ignored issues
show
Documentation introduced by
The doc-type array{_page?: could not be parsed: Unknown type name "array{_page?:" at position 0. (view supported doc-types)

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

Loading history...
2391
     */
2392
    final protected function getDefaultSortValues(): array
2393
    {
2394
        $defaultSortValues = [];
2395
2396
        $this->configureDefaultSortValues($defaultSortValues);
2397
2398
        foreach ($this->getExtensions() as $extension) {
2399
            $extension->configureDefaultSortValues($this, $defaultSortValues);
2400
        }
2401
2402
        return $defaultSortValues;
2403
    }
2404
2405
    /**
2406
     * Returns a list of default filters.
2407
     */
2408
    final protected function getDefaultFilterValues(): array
2409
    {
2410
        $defaultFilterValues = [];
2411
2412
        $this->configureDefaultFilterValues($defaultFilterValues);
2413
2414
        foreach ($this->getExtensions() as $extension) {
2415
            $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2416
        }
2417
2418
        return $defaultFilterValues;
2419
    }
2420
2421
    protected function configureFormFields(FormMapper $form): void
2422
    {
2423
    }
2424
2425
    protected function configureListFields(ListMapper $list): void
2426
    {
2427
    }
2428
2429
    protected function configureDatagridFilters(DatagridMapper $filter): void
2430
    {
2431
    }
2432
2433
    protected function configureShowFields(ShowMapper $show): void
2434
    {
2435
    }
2436
2437
    protected function configureRoutes(RouteCollection $collection): void
2438
    {
2439
    }
2440
2441
    /**
2442
     * Allows you to customize batch actions.
2443
     *
2444
     * @param array $actions List of actions
2445
     */
2446
    protected function configureBatchActions(array $actions): array
2447
    {
2448
        return $actions;
2449
    }
2450
2451
    /**
2452
     * Configures the tab menu in your admin.
2453
     */
2454
    protected function configureTabMenu(ItemInterface $menu, string $action, ?AdminInterface $childAdmin = null): void
0 ignored issues
show
Unused Code introduced by
The parameter $menu is not used and could be removed.

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

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

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

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

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

Loading history...
2455
    {
2456
    }
2457
2458
    /**
2459
     * build the view FieldDescription array.
2460
     */
2461
    protected function buildShow(): void
2462
    {
2463
        if ($this->loaded['show']) {
2464
            return;
2465
        }
2466
2467
        $this->loaded['show'] = true;
2468
2469
        $this->show = $this->getShowBuilder()->getBaseList();
2470
        $mapper = new ShowMapper($this->getShowBuilder(), $this->show, $this);
2471
2472
        $this->configureShowFields($mapper);
2473
2474
        foreach ($this->getExtensions() as $extension) {
2475
            $extension->configureShowFields($mapper);
2476
        }
2477
    }
2478
2479
    /**
2480
     * build the list FieldDescription array.
2481
     */
2482
    protected function buildList(): void
2483
    {
2484
        if ($this->loaded['list']) {
2485
            return;
2486
        }
2487
2488
        $this->loaded['list'] = true;
2489
2490
        $this->list = $this->getListBuilder()->getBaseList();
2491
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
2492
2493
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
2494
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2495
                $this->getClass(),
2496
                'batch',
2497
                [
2498
                    'label' => 'batch',
2499
                    'code' => '_batch',
2500
                    'sortable' => false,
2501
                    'virtual_field' => true,
2502
                ]
2503
            );
2504
2505
            $fieldDescription->setAdmin($this);
2506
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
2507
2508
            $mapper->add($fieldDescription, ListMapper::TYPE_BATCH);
2509
        }
2510
2511
        $this->configureListFields($mapper);
2512
2513
        foreach ($this->getExtensions() as $extension) {
2514
            $extension->configureListFields($mapper);
2515
        }
2516
2517
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
2518
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2519
                $this->getClass(),
2520
                'select',
2521
                [
2522
                    'label' => false,
2523
                    'code' => '_select',
2524
                    'sortable' => false,
2525
                    'virtual_field' => false,
2526
                ]
2527
            );
2528
2529
            $fieldDescription->setAdmin($this);
2530
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
2531
2532
            $mapper->add($fieldDescription, ListMapper::TYPE_SELECT);
2533
        }
2534
    }
2535
2536
    /**
2537
     * Build the form FieldDescription collection.
2538
     */
2539
    protected function buildForm(): void
2540
    {
2541
        if ($this->loaded['form']) {
2542
            return;
2543
        }
2544
2545
        $this->loaded['form'] = true;
2546
2547
        $formBuilder = $this->getFormBuilder();
2548
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
2549
            $this->preValidate($event->getData());
2550
        }, 100);
2551
2552
        $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...
2553
    }
2554
2555
    /**
2556
     * Gets the subclass corresponding to the given name.
2557
     *
2558
     * @param string $name The name of the sub class
2559
     *
2560
     * @return string the subclass
2561
     */
2562
    protected function getSubClass(string $name): string
2563
    {
2564
        if ($this->hasSubClass($name)) {
2565
            return $this->subClasses[$name];
2566
        }
2567
2568
        throw new \LogicException(sprintf('Unable to find the subclass `%s` for admin `%s`', $name, static::class));
2569
    }
2570
2571
    /**
2572
     * Attach the inline validator to the model metadata, this must be done once per admin.
2573
     */
2574
    protected function attachInlineValidator(): void
2575
    {
2576
        $admin = $this;
2577
2578
        // add the custom inline validation option
2579
        $metadata = $this->validator->getMetadataFor($this->getClass());
2580
        if (!$metadata instanceof GenericMetadata) {
2581
            throw new \UnexpectedValueException(
2582
                sprintf(
2583
                    'Cannot add inline validator for %s because its metadata is an instance of %s instead of %s',
2584
                    $this->getClass(),
2585
                    \get_class($metadata),
2586
                    GenericMetadata::class
2587
                )
2588
            );
2589
        }
2590
2591
        $metadata->addConstraint(new InlineConstraint([
2592
            'service' => $this,
2593
            'method' => static function (ErrorElement $errorElement, $object) use ($admin): void {
2594
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
2595
2596
                // This avoid the main validation to be cascaded to children
2597
                // The problem occurs when a model Page has a collection of Page as property
2598
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
2599
                    return;
2600
                }
2601
2602
                $admin->validate($errorElement, $object);
2603
2604
                foreach ($admin->getExtensions() as $extension) {
2605
                    $extension->validate($admin, $errorElement, $object);
2606
                }
2607
            },
2608
            'serializingWarning' => true,
2609
        ]));
2610
    }
2611
2612
    /**
2613
     * Return list routes with permissions name.
2614
     *
2615
     * @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...
2616
     */
2617
    protected function getAccess(): array
2618
    {
2619
        $access = array_merge([
2620
            'acl' => 'MASTER',
2621
            'export' => 'EXPORT',
2622
            'historyCompareRevisions' => 'EDIT',
2623
            'historyViewRevision' => 'EDIT',
2624
            'history' => 'EDIT',
2625
            'edit' => 'EDIT',
2626
            'show' => 'VIEW',
2627
            'create' => 'CREATE',
2628
            'delete' => 'DELETE',
2629
            'batchDelete' => 'DELETE',
2630
            'list' => 'LIST',
2631
        ], $this->getAccessMapping());
2632
2633
        foreach ($this->extensions as $extension) {
2634
            $access = array_merge($access, $extension->getAccessMapping($this));
2635
        }
2636
2637
        return $access;
2638
    }
2639
2640
    /**
2641
     * Configures a list of default filters.
2642
     */
2643
    protected function configureDefaultFilterValues(array &$filterValues): void
2644
    {
2645
    }
2646
2647
    /**
2648
     * Configures a list of default sort values.
2649
     *
2650
     * Example:
2651
     *   $sortValues['_sort_by'] = 'foo'
2652
     *   $sortValues['_sort_order'] = 'DESC'
2653
     */
2654
    protected function configureDefaultSortValues(array &$sortValues)
0 ignored issues
show
Unused Code introduced by
The parameter $sortValues is not used and could be removed.

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

Loading history...
2655
    {
2656
    }
2657
2658
    /**
2659
     * Set the parent object, if any, to the provided object.
2660
     */
2661
    final protected function appendParentObject(object $object): void
2662
    {
2663
        if ($this->isChild() && $this->getParentAssociationMapping()) {
2664
            $parentAdmin = $this->getParent();
2665
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
2666
2667
            if (null !== $parentObject) {
2668
                $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
2669
                $propertyPath = new PropertyPath($this->getParentAssociationMapping());
2670
2671
                $value = $propertyAccessor->getValue($object, $propertyPath);
2672
2673
                if (\is_array($value) || $value instanceof \ArrayAccess) {
2674
                    $value[] = $parentObject;
2675
                    $propertyAccessor->setValue($object, $propertyPath, $value);
2676
                } else {
2677
                    $propertyAccessor->setValue($object, $propertyPath, $parentObject);
2678
                }
2679
            }
2680
        } elseif ($this->hasParentFieldDescription()) {
2681
            $parentAdmin = $this->getParentFieldDescription()->getAdmin();
2682
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
2683
2684
            if (null !== $parentObject) {
2685
                ObjectManipulator::setObject($object, $parentObject, $this->getParentFieldDescription());
2686
            }
2687
        }
2688
    }
2689
2690
    /**
2691
     * {@inheritdoc}
2692
     */
2693
    private function buildDatagrid(): void
2694
    {
2695
        if ($this->loaded['datagrid']) {
2696
            return;
2697
        }
2698
2699
        $this->loaded['datagrid'] = true;
2700
2701
        $filterParameters = $this->getFilterParameters();
2702
2703
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
2704
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
2705
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
2706
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
2707
            } else {
2708
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
2709
                    $this->getClass(),
2710
                    $filterParameters['_sort_by'],
2711
                    []
2712
                );
2713
2714
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
2715
            }
2716
        }
2717
2718
        // initialize the datagrid
2719
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
2720
2721
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
2722
2723
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
2724
2725
        // build the datagrid filter
2726
        $this->configureDatagridFilters($mapper);
2727
2728
        // ok, try to limit to add parent filter
2729
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
2730
            $mapper->add($this->getParentAssociationMapping(), null, [
2731
                'show_filter' => false,
2732
                'label' => false,
2733
                'field_type' => ModelHiddenType::class,
2734
                'field_options' => [
2735
                    'model_manager' => $this->getModelManager(),
2736
                ],
2737
                'operator_type' => HiddenType::class,
2738
            ], null, null, [
2739
                'admin_code' => $this->getParent()->getCode(),
2740
            ]);
2741
        }
2742
2743
        foreach ($this->getExtensions() as $extension) {
2744
            $extension->configureDatagridFilters($mapper);
2745
        }
2746
    }
2747
2748
    /**
2749
     * Build all the related urls to the current admin.
2750
     */
2751
    private function buildRoutes(): void
2752
    {
2753
        if ($this->loaded['routes']) {
2754
            return;
2755
        }
2756
2757
        $this->loaded['routes'] = true;
2758
2759
        $this->routes = new RouteCollection(
2760
            $this->getBaseCodeRoute(),
2761
            $this->getBaseRouteName(),
2762
            $this->getBaseRoutePattern(),
2763
            $this->getBaseControllerName()
2764
        );
2765
2766
        $this->routeBuilder->build($this, $this->routes);
2767
2768
        $this->configureRoutes($this->routes);
2769
2770
        foreach ($this->getExtensions() as $extension) {
2771
            $extension->configureRoutes($this, $this->routes);
2772
        }
2773
    }
2774
}
2775
2776
class_exists(ErrorElement::class);
2777