Completed
Pull Request — master (#6210)
by Jordi Sala
03:00
created

AbstractAdmin::getFilterParameters()   B

Complexity

Conditions 11
Paths 81

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

Loading history...
544
545
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
546
            // No translation key exists
547
            if ($transLabel === $label) {
548
                $fields[$key] = $field;
549
            } else {
550
                $fields[$transLabel] = $field;
551
            }
552
        }
553
554
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
555
    }
556
557
    public function validate(ErrorElement $errorElement, $object): void
558
    {
559
    }
560
561
    /**
562
     * define custom variable.
563
     */
564
    public function initialize(): void
565
    {
566
        if (!$this->classnameLabel) {
567
            $this->classnameLabel = substr($this->getClass(), strrpos($this->getClass(), '\\') + 1);
568
        }
569
570
        $this->configure();
571
    }
572
573
    public function update(object $object): object
574
    {
575
        $this->preUpdate($object);
576
        foreach ($this->extensions as $extension) {
577
            $extension->preUpdate($this, $object);
578
        }
579
580
        $result = $this->getModelManager()->update($object);
581
        // BC compatibility
582
        if (null !== $result) {
583
            $object = $result;
584
        }
585
586
        $this->postUpdate($object);
587
        foreach ($this->extensions as $extension) {
588
            $extension->postUpdate($this, $object);
589
        }
590
591
        return $object;
592
    }
593
594
    public function create(object $object): object
595
    {
596
        $this->prePersist($object);
597
        foreach ($this->extensions as $extension) {
598
            $extension->prePersist($this, $object);
599
        }
600
601
        $result = $this->getModelManager()->create($object);
602
        // BC compatibility
603
        if (null !== $result) {
604
            $object = $result;
605
        }
606
607
        $this->postPersist($object);
608
        foreach ($this->extensions as $extension) {
609
            $extension->postPersist($this, $object);
610
        }
611
612
        $this->createObjectSecurity($object);
613
614
        return $object;
615
    }
616
617
    public function delete(object $object): void
618
    {
619
        $this->preRemove($object);
620
        foreach ($this->extensions as $extension) {
621
            $extension->preRemove($this, $object);
622
        }
623
624
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
625
        $this->getModelManager()->delete($object);
626
627
        $this->postRemove($object);
628
        foreach ($this->extensions as $extension) {
629
            $extension->postRemove($this, $object);
630
        }
631
    }
632
633
    public function preValidate(object $object): void
634
    {
635
    }
636
637
    public function preUpdate(object $object): void
638
    {
639
    }
640
641
    public function postUpdate(object $object): void
642
    {
643
    }
644
645
    public function prePersist(object $object): void
646
    {
647
    }
648
649
    public function postPersist(object $object): void
650
    {
651
    }
652
653
    public function preRemove(object $object): void
654
    {
655
    }
656
657
    public function postRemove(object $object): void
658
    {
659
    }
660
661
    public function preBatchAction(string $actionName, ProxyQueryInterface $query, array &$idx, bool $allElements = false): void
662
    {
663
    }
664
665
    public function getFilterParameters(): array
666
    {
667
        $parameters = [];
668
669
        // build the values array
670
        if ($this->hasRequest()) {
671
            $filters = $this->request->query->get('filter', []);
672
            if (isset($filters['_page'])) {
673
                $filters['_page'] = (int) $filters['_page'];
674
            }
675
            if (isset($filters['_per_page'])) {
676
                $filters['_per_page'] = (int) $filters['_per_page'];
677
            }
678
679
            // if filter persistence is configured
680
            if (null !== $this->filterPersister) {
681
                // if reset filters is asked, remove from storage
682
                if ('reset' === $this->request->query->get('filters')) {
683
                    $this->filterPersister->reset($this->getCode());
684
                }
685
686
                // if no filters, fetch from storage
687
                // otherwise save to storage
688
                if (empty($filters)) {
689
                    $filters = $this->filterPersister->get($this->getCode());
690
                } else {
691
                    $this->filterPersister->set($this->getCode(), $filters);
692
                }
693
            }
694
695
            $parameters = array_merge(
696
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
697
                $this->getDefaultSortValues(),
698
                $this->getDefaultFilterValues(),
699
                $filters
700
            );
701
702
            if (!isset($parameters['_per_page']) || !$this->determinedPerPageValue($parameters['_per_page'])) {
703
                $parameters['_per_page'] = $this->getMaxPerPage();
704
            }
705
706
            // always force the parent value
707
            if ($this->isChild() && $this->getParentAssociationMapping()) {
708
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
709
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
710
            }
711
        }
712
713
        return $parameters;
714
    }
715
716
    /**
717
     * Returns the name of the parent related field, so the field can be use to set the default
718
     * value (ie the parent object) or to filter the object.
719
     *
720
     * @throws \InvalidArgumentException
721
     */
722
    public function getParentAssociationMapping(): ?string
723
    {
724
        if ($this->isChild()) {
725
            $parent = $this->getParent()->getCode();
726
727
            if (\array_key_exists($parent, $this->parentAssociationMapping)) {
728
                return $this->parentAssociationMapping[$parent];
729
            }
730
731
            throw new \InvalidArgumentException(sprintf(
732
                'There\'s no association between %s and %s.',
733
                $this->getCode(),
734
                $this->getParent()->getCode()
735
            ));
736
        }
737
738
        return null;
739
    }
740
741
    final public function addParentAssociationMapping(string $code, string $value): void
742
    {
743
        $this->parentAssociationMapping[$code] = $value;
744
    }
745
746
    /**
747
     * Returns the baseRoutePattern used to generate the routing information.
748
     *
749
     * @throws \RuntimeException
750
     *
751
     * @return string the baseRoutePattern used to generate the routing information
752
     */
753
    public function getBaseRoutePattern(): string
754
    {
755
        if (null !== $this->cachedBaseRoutePattern) {
756
            return $this->cachedBaseRoutePattern;
757
        }
758
759
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
760
            $baseRoutePattern = $this->baseRoutePattern;
761
            if (!$this->baseRoutePattern) {
762
                preg_match(self::CLASS_REGEX, $this->class, $matches);
763
764
                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...
765
                    throw new \RuntimeException(sprintf(
766
                        'Please define a default `baseRoutePattern` value for the admin class `%s`',
767
                        static::class
768
                    ));
769
                }
770
                $baseRoutePattern = $this->urlize($matches[5], '-');
771
            }
772
773
            $this->cachedBaseRoutePattern = sprintf(
774
                '%s/%s/%s',
775
                $this->getParent()->getBaseRoutePattern(),
776
                $this->getParent()->getRouterIdParameter(),
777
                $baseRoutePattern
778
            );
779
        } elseif ($this->baseRoutePattern) {
780
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
781
        } else {
782
            preg_match(self::CLASS_REGEX, $this->class, $matches);
783
784
            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...
785
                throw new \RuntimeException(sprintf(
786
                    'Please define a default `baseRoutePattern` value for the admin class `%s`',
787
                    static::class
788
                ));
789
            }
790
791
            $this->cachedBaseRoutePattern = sprintf(
792
                '/%s%s/%s',
793
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
794
                $this->urlize($matches[3], '-'),
795
                $this->urlize($matches[5], '-')
796
            );
797
        }
798
799
        return $this->cachedBaseRoutePattern;
800
    }
801
802
    /**
803
     * Returns the baseRouteName used to generate the routing information.
804
     *
805
     * @throws \RuntimeException
806
     *
807
     * @return string the baseRouteName used to generate the routing information
808
     */
809
    public function getBaseRouteName(): string
810
    {
811
        if (null !== $this->cachedBaseRouteName) {
812
            return $this->cachedBaseRouteName;
813
        }
814
815
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
816
            $baseRouteName = $this->baseRouteName;
817
            if (!$this->baseRouteName) {
818
                preg_match(self::CLASS_REGEX, $this->class, $matches);
819
820
                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...
821
                    throw new \RuntimeException(sprintf(
822
                        'Cannot automatically determine base route name,'
823
                        .' please define a default `baseRouteName` value for the admin class `%s`',
824
                        static::class
825
                    ));
826
                }
827
                $baseRouteName = $this->urlize($matches[5]);
828
            }
829
830
            $this->cachedBaseRouteName = sprintf(
831
                '%s_%s',
832
                $this->getParent()->getBaseRouteName(),
833
                $baseRouteName
834
            );
835
        } elseif ($this->baseRouteName) {
836
            $this->cachedBaseRouteName = $this->baseRouteName;
837
        } else {
838
            preg_match(self::CLASS_REGEX, $this->class, $matches);
839
840
            if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
841
                throw new \RuntimeException(sprintf(
842
                    'Cannot automatically determine base route name,'
843
                    .' please define a default `baseRouteName` value for the admin class `%s`',
844
                    static::class
845
                ));
846
            }
847
848
            $this->cachedBaseRouteName = sprintf(
849
                'admin_%s%s_%s',
850
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
851
                $this->urlize($matches[3]),
852
                $this->urlize($matches[5])
853
            );
854
        }
855
856
        return $this->cachedBaseRouteName;
857
    }
858
859
    public function getClass(): string
860
    {
861
        if ($this->hasActiveSubClass()) {
862
            if ($this->hasParentFieldDescription()) {
863
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
864
            }
865
866
            $subClass = $this->getRequest()->query->get('subclass');
867
868
            if (!$this->hasSubClass($subClass)) {
869
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
870
            }
871
872
            return $this->getSubClass($subClass);
873
        }
874
875
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
876
        if ($this->subject && \is_object($this->subject)) {
877
            return ClassUtils::getClass($this->subject);
878
        }
879
880
        return $this->class;
881
    }
882
883
    public function getSubClasses(): array
884
    {
885
        return $this->subClasses;
886
    }
887
888
    public function setSubClasses(array $subClasses): void
889
    {
890
        $this->subClasses = $subClasses;
891
    }
892
893
    public function hasSubClass(string $name): bool
894
    {
895
        return isset($this->subClasses[$name]);
896
    }
897
898
    public function hasActiveSubClass(): bool
899
    {
900
        if (\count($this->subClasses) > 0 && $this->request) {
901
            return null !== $this->getRequest()->query->get('subclass');
902
        }
903
904
        return false;
905
    }
906
907
    public function getActiveSubClass(): string
908
    {
909
        if (!$this->hasActiveSubClass()) {
910
            throw new \LogicException(sprintf(
911
                'Admin "%s" has no active subclass.',
912
                static::class
913
            ));
914
        }
915
916
        return $this->getSubClass($this->getActiveSubclassCode());
917
    }
918
919
    public function getActiveSubclassCode(): string
920
    {
921
        if (!$this->hasActiveSubClass()) {
922
            throw new \LogicException(sprintf(
923
                'Admin "%s" has no active subclass.',
924
                static::class
925
            ));
926
        }
927
928
        $subClass = $this->getRequest()->query->get('subclass');
929
930
        if (!$this->hasSubClass($subClass)) {
931
            throw new \LogicException(sprintf(
932
                'Admin "%s" has no active subclass.',
933
                static::class
934
            ));
935
        }
936
937
        return $subClass;
938
    }
939
940
    public function getBatchActions(): array
941
    {
942
        $actions = [];
943
944
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
945
            $actions['delete'] = [
946
                'label' => 'action_delete',
947
                'translation_domain' => 'SonataAdminBundle',
948
                'ask_confirmation' => true, // by default always true
949
            ];
950
        }
951
952
        $actions = $this->configureBatchActions($actions);
953
954
        foreach ($this->getExtensions() as $extension) {
955
            $actions = $extension->configureBatchActions($this, $actions);
956
        }
957
958
        foreach ($actions  as $name => &$action) {
959
            if (!\array_key_exists('label', $action)) {
960
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
961
            }
962
963
            if (!\array_key_exists('translation_domain', $action)) {
964
                $action['translation_domain'] = $this->getTranslationDomain();
965
            }
966
        }
967
968
        return $actions;
969
    }
970
971
    public function getRoutes(): RouteCollectionInterface
972
    {
973
        $this->buildRoutes();
974
975
        return $this->routes;
976
    }
977
978
    public function getRouterIdParameter(): string
979
    {
980
        return sprintf('{%s}', $this->getIdParameter());
981
    }
982
983
    public function getIdParameter(): string
984
    {
985
        $parameter = 'id';
986
987
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
988
            $parameter = sprintf('child%s', ucfirst($parameter));
989
        }
990
991
        return $parameter;
992
    }
993
994
    public function hasRoute(string $name): bool
995
    {
996
        if (!$this->routeGenerator) {
997
            throw new \RuntimeException('RouteGenerator cannot be null');
998
        }
999
1000
        return $this->routeGenerator->hasAdminRoute($this, $name);
1001
    }
1002
1003
    public function isCurrentRoute(string $name, ?string $adminCode = null): bool
1004
    {
1005
        if (!$this->hasRequest()) {
1006
            return false;
1007
        }
1008
1009
        $request = $this->getRequest();
1010
        $route = $request->get('_route');
1011
1012
        if ($adminCode) {
1013
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1014
        } else {
1015
            $admin = $this;
1016
        }
1017
1018
        if (!$admin) {
1019
            return false;
1020
        }
1021
1022
        return sprintf('%s_%s', $admin->getBaseRouteName(), $name) === $route;
1023
    }
1024
1025
    public function generateObjectUrl(string $name, object $object, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1026
    {
1027
        $parameters['id'] = $this->getUrlSafeIdentifier($object);
1028
1029
        return $this->generateUrl($name, $parameters, $referenceType);
1030
    }
1031
1032
    public function generateUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1033
    {
1034
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $referenceType);
1035
    }
1036
1037
    public function generateMenuUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): array
1038
    {
1039
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $referenceType);
1040
    }
1041
1042
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry): void
1043
    {
1044
        $this->templateRegistry = $templateRegistry;
1045
    }
1046
1047
    /**
1048
     * @param array<string, string> $templates
1049
     */
1050
    public function setTemplates(array $templates): void
1051
    {
1052
        $this->getTemplateRegistry()->setTemplates($templates);
1053
    }
1054
1055
    /**
1056
     * {@inheritdoc}
1057
     */
1058
    public function setTemplate(string $name, string $template): void
1059
    {
1060
        $this->getTemplateRegistry()->setTemplate($name, $template);
1061
    }
1062
1063
    public function getNewInstance(): object
1064
    {
1065
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1066
1067
        $this->appendParentObject($object);
1068
1069
        foreach ($this->getExtensions() as $extension) {
1070
            $extension->alterNewInstance($this, $object);
1071
        }
1072
1073
        return $object;
1074
    }
1075
1076
    public function getFormBuilder(): FormBuilderInterface
1077
    {
1078
        $this->formOptions['data_class'] = $this->getClass();
1079
1080
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1081
            $this->getUniqid(),
1082
            $this->formOptions
1083
        );
1084
1085
        $this->defineFormBuilder($formBuilder);
1086
1087
        return $formBuilder;
1088
    }
1089
1090
    /**
1091
     * This method is being called by the main admin class and the child class,
1092
     * the getFormBuilder is only call by the main admin class.
1093
     */
1094
    public function defineFormBuilder(FormBuilderInterface $formBuilder): void
1095
    {
1096
        if (!$this->hasSubject()) {
1097
            throw new \LogicException(sprintf(
1098
                'Admin "%s" has no subject.',
1099
                static::class
1100
            ));
1101
        }
1102
1103
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1104
1105
        $this->configureFormFields($mapper);
1106
1107
        foreach ($this->getExtensions() as $extension) {
1108
            $extension->configureFormFields($mapper);
1109
        }
1110
1111
        $this->attachInlineValidator();
1112
    }
1113
1114
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription): void
1115
    {
1116
        $pool = $this->getConfigurationPool();
1117
1118
        $adminCode = $fieldDescription->getOption('admin_code');
1119
1120
        if (null !== $adminCode) {
1121
            if (!$pool->hasAdminByAdminCode($adminCode)) {
1122
                return;
1123
            }
1124
1125
            $admin = $pool->getAdminByAdminCode($adminCode);
1126
        } else {
1127
            $targetModel = $fieldDescription->getTargetModel();
1128
1129
            if (!$pool->hasAdminByClass($targetModel)) {
1130
                return;
1131
            }
1132
1133
            $admin = $pool->getAdminByClass($targetModel);
1134
        }
1135
1136
        if ($this->hasRequest()) {
1137
            $admin->setRequest($this->getRequest());
1138
        }
1139
1140
        $fieldDescription->setAssociationAdmin($admin);
1141
    }
1142
1143
    public function getObject($id): ?object
1144
    {
1145
        $object = $this->getModelManager()->find($this->getClass(), $id);
1146
        foreach ($this->getExtensions() as $extension) {
1147
            $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...
1148
        }
1149
1150
        return $object;
1151
    }
1152
1153
    public function getForm(): ?FormInterface
1154
    {
1155
        $this->buildForm();
1156
1157
        return $this->form;
1158
    }
1159
1160
    public function getList(): ?FieldDescriptionCollection
1161
    {
1162
        $this->buildList();
1163
1164
        return $this->list;
1165
    }
1166
1167
    final public function createQuery(): ProxyQueryInterface
1168
    {
1169
        $query = $this->getModelManager()->createQuery($this->getClass());
1170
1171
        $query = $this->configureQuery($query);
1172
        foreach ($this->extensions as $extension) {
1173
            $extension->configureQuery($this, $query);
1174
        }
1175
1176
        return $query;
1177
    }
1178
1179
    public function getDatagrid(): DatagridInterface
1180
    {
1181
        $this->buildDatagrid();
1182
1183
        return $this->datagrid;
1184
    }
1185
1186
    public function buildTabMenu(string $action, ?AdminInterface $childAdmin = null): ItemInterface
1187
    {
1188
        if ($this->loaded['tab_menu']) {
1189
            return $this->menu;
1190
        }
1191
1192
        $this->loaded['tab_menu'] = true;
1193
1194
        $menu = $this->menuFactory->createItem('root');
1195
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1196
        $menu->setExtra('translation_domain', $this->translationDomain);
1197
1198
        // Prevents BC break with KnpMenuBundle v1.x
1199
        if (method_exists($menu, 'setCurrentUri')) {
1200
            $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...
1201
        }
1202
1203
        $this->configureTabMenu($menu, $action, $childAdmin);
1204
1205
        foreach ($this->getExtensions() as $extension) {
1206
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1207
        }
1208
1209
        $this->menu = $menu;
1210
1211
        return $this->menu;
1212
    }
1213
1214
    public function getSideMenu(string $action, ?AdminInterface $childAdmin = null): ItemInterface
1215
    {
1216
        if ($this->isChild()) {
1217
            return $this->getParent()->getSideMenu($action, $this);
1218
        }
1219
1220
        $this->buildTabMenu($action, $childAdmin);
1221
1222
        return $this->menu;
1223
    }
1224
1225
    public function getRootCode(): string
1226
    {
1227
        return $this->getRoot()->getCode();
1228
    }
1229
1230
    public function getRoot(): AdminInterface
1231
    {
1232
        if (!$this->hasParentFieldDescription()) {
1233
            return $this;
1234
        }
1235
1236
        return $this->getParentFieldDescription()->getAdmin()->getRoot();
1237
    }
1238
1239
    public function setBaseControllerName(string $baseControllerName): void
1240
    {
1241
        $this->baseControllerName = $baseControllerName;
1242
    }
1243
1244
    public function getBaseControllerName(): string
1245
    {
1246
        return $this->baseControllerName;
1247
    }
1248
1249
    public function setLabel(?string $label): void
1250
    {
1251
        $this->label = $label;
1252
    }
1253
1254
    public function getLabel(): ?string
1255
    {
1256
        return $this->label;
1257
    }
1258
1259
    public function setFilterPersister(?FilterPersisterInterface $filterPersister = null): void
1260
    {
1261
        $this->filterPersister = $filterPersister;
1262
    }
1263
1264
    public function getMaxPerPage(): int
1265
    {
1266
        $sortValues = $this->getModelManager()->getDefaultSortValues($this->class);
1267
1268
        return $sortValues['_per_page'] ?? 25;
1269
    }
1270
1271
    public function setMaxPageLinks(int $maxPageLinks): void
1272
    {
1273
        $this->maxPageLinks = $maxPageLinks;
1274
    }
1275
1276
    public function getMaxPageLinks(): int
1277
    {
1278
        return $this->maxPageLinks;
1279
    }
1280
1281
    public function getFormGroups(): array
1282
    {
1283
        return $this->formGroups;
1284
    }
1285
1286
    public function setFormGroups(array $formGroups): void
1287
    {
1288
        $this->formGroups = $formGroups;
1289
    }
1290
1291
    public function removeFieldFromFormGroup(string $key): void
1292
    {
1293
        foreach ($this->formGroups as $name => $formGroup) {
1294
            unset($this->formGroups[$name]['fields'][$key]);
1295
1296
            if (empty($this->formGroups[$name]['fields'])) {
1297
                unset($this->formGroups[$name]);
1298
            }
1299
        }
1300
    }
1301
1302
    public function reorderFormGroup(string $group, array $keys): void
1303
    {
1304
        $formGroups = $this->getFormGroups();
1305
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1306
        $this->setFormGroups($formGroups);
1307
    }
1308
1309
    public function getFormTabs(): array
1310
    {
1311
        return $this->formTabs;
1312
    }
1313
1314
    public function setFormTabs(array $formTabs): void
1315
    {
1316
        $this->formTabs = $formTabs;
1317
    }
1318
1319
    public function getShowTabs(): array
1320
    {
1321
        return $this->showTabs;
1322
    }
1323
1324
    public function setShowTabs(array $showTabs): void
1325
    {
1326
        $this->showTabs = $showTabs;
1327
    }
1328
1329
    public function getShowGroups(): array
1330
    {
1331
        return $this->showGroups;
1332
    }
1333
1334
    public function setShowGroups(array $showGroups): void
1335
    {
1336
        $this->showGroups = $showGroups;
1337
    }
1338
1339
    public function reorderShowGroup(string $group, array $keys): void
1340
    {
1341
        $showGroups = $this->getShowGroups();
1342
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1343
        $this->setShowGroups($showGroups);
1344
    }
1345
1346
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription): void
1347
    {
1348
        $this->parentFieldDescription = $parentFieldDescription;
1349
    }
1350
1351
    public function getParentFieldDescription(): FieldDescriptionInterface
1352
    {
1353
        if (!$this->hasParentFieldDescription()) {
1354
            throw new \LogicException(sprintf(
1355
                'Admin "%s" has no parent field description.',
1356
                static::class
1357
            ));
1358
        }
1359
1360
        return $this->parentFieldDescription;
1361
    }
1362
1363
    public function hasParentFieldDescription(): bool
1364
    {
1365
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1366
    }
1367
1368
    public function setSubject(?object $subject): void
1369
    {
1370
        if (\is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1371
            throw new \LogicException(sprintf(
1372
                'Admin "%s" does not allow this subject: %s, use the one register with this admin class %s',
1373
                static::class,
1374
                \get_class($subject),
1375
                $this->getClass()
1376
            ));
1377
        }
1378
1379
        $this->subject = $subject;
1380
    }
1381
1382
    public function getSubject(): object
1383
    {
1384
        if (!$this->hasSubject()) {
1385
            throw new \LogicException(sprintf(
1386
                'Admin "%s" has no subject.',
1387
                static::class
1388
            ));
1389
        }
1390
1391
        return $this->subject;
1392
    }
1393
1394
    public function hasSubject(): bool
1395
    {
1396
        if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
1397
            $id = $this->request->get($this->getIdParameter());
1398
1399
            if (null !== $id) {
1400
                $this->subject = $this->getObject($id);
1401
            }
1402
        }
1403
1404
        return null !== $this->subject;
1405
    }
1406
1407
    public function getFormFieldDescriptions(): array
1408
    {
1409
        $this->buildForm();
1410
1411
        return $this->formFieldDescriptions;
1412
    }
1413
1414
    public function getFormFieldDescription(string $name): FieldDescriptionInterface
1415
    {
1416
        $this->buildForm();
1417
1418
        if (!$this->hasFormFieldDescription($name)) {
1419
            throw new \LogicException(sprintf(
1420
                'Admin "%s" has no form field description for the field %s.',
1421
                static::class,
1422
                $name
1423
            ));
1424
        }
1425
1426
        return $this->formFieldDescriptions[$name];
1427
    }
1428
1429
    /**
1430
     * Returns true if the admin has a FieldDescription with the given $name.
1431
     */
1432
    public function hasFormFieldDescription(string $name): bool
1433
    {
1434
        $this->buildForm();
1435
1436
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1437
    }
1438
1439
    public function addFormFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1440
    {
1441
        $this->formFieldDescriptions[$name] = $fieldDescription;
1442
    }
1443
1444
    /**
1445
     * remove a FieldDescription.
1446
     */
1447
    public function removeFormFieldDescription(string $name): void
1448
    {
1449
        unset($this->formFieldDescriptions[$name]);
1450
    }
1451
1452
    /**
1453
     * build and return the collection of form FieldDescription.
1454
     *
1455
     * @return FieldDescriptionInterface[] collection of form FieldDescription
1456
     */
1457
    public function getShowFieldDescriptions(): array
1458
    {
1459
        $this->buildShow();
1460
1461
        return $this->showFieldDescriptions;
1462
    }
1463
1464
    /**
1465
     * Returns the form FieldDescription with the given $name.
1466
     */
1467
    public function getShowFieldDescription(string $name): FieldDescriptionInterface
1468
    {
1469
        $this->buildShow();
1470
1471
        if (!$this->hasShowFieldDescription($name)) {
1472
            throw new \LogicException(sprintf(
1473
                'Admin "%s" has no show field description for the field %s.',
1474
                static::class,
1475
                $name
1476
            ));
1477
        }
1478
1479
        return $this->showFieldDescriptions[$name];
1480
    }
1481
1482
    public function hasShowFieldDescription(string $name): bool
1483
    {
1484
        $this->buildShow();
1485
1486
        return \array_key_exists($name, $this->showFieldDescriptions);
1487
    }
1488
1489
    public function addShowFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1490
    {
1491
        $this->showFieldDescriptions[$name] = $fieldDescription;
1492
    }
1493
1494
    public function removeShowFieldDescription(string $name): void
1495
    {
1496
        unset($this->showFieldDescriptions[$name]);
1497
    }
1498
1499
    public function getListFieldDescriptions(): array
1500
    {
1501
        $this->buildList();
1502
1503
        return $this->listFieldDescriptions;
1504
    }
1505
1506
    public function getListFieldDescription(string $name): FieldDescriptionInterface
1507
    {
1508
        $this->buildList();
1509
1510
        if (!$this->hasListFieldDescription($name)) {
1511
            throw new \LogicException(sprintf(
1512
                'Admin "%s" has no list field description for %s.',
1513
                static::class,
1514
                $name
1515
            ));
1516
        }
1517
1518
        return $this->listFieldDescriptions[$name];
1519
    }
1520
1521
    public function hasListFieldDescription(string $name): bool
1522
    {
1523
        $this->buildList();
1524
1525
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1526
    }
1527
1528
    public function addListFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1529
    {
1530
        $this->listFieldDescriptions[$name] = $fieldDescription;
1531
    }
1532
1533
    public function removeListFieldDescription(string $name): void
1534
    {
1535
        unset($this->listFieldDescriptions[$name]);
1536
    }
1537
1538
    public function getFilterFieldDescription(string $name): FieldDescriptionInterface
1539
    {
1540
        $this->buildDatagrid();
1541
1542
        if (!$this->hasFilterFieldDescription($name)) {
1543
            throw new \LogicException(sprintf(
1544
                'Admin "%s" has no filter field description for the field %s.',
1545
                static::class,
1546
                $name
1547
            ));
1548
        }
1549
1550
        return $this->filterFieldDescriptions[$name];
1551
    }
1552
1553
    public function hasFilterFieldDescription(string $name): bool
1554
    {
1555
        $this->buildDatagrid();
1556
1557
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1558
    }
1559
1560
    public function addFilterFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1561
    {
1562
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1563
    }
1564
1565
    public function removeFilterFieldDescription(string $name): void
1566
    {
1567
        unset($this->filterFieldDescriptions[$name]);
1568
    }
1569
1570
    public function getFilterFieldDescriptions(): array
1571
    {
1572
        $this->buildDatagrid();
1573
1574
        return $this->filterFieldDescriptions;
1575
    }
1576
1577
    public function addChild(AdminInterface $child, string $field): void
1578
    {
1579
        $parentAdmin = $this;
1580
        while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
1581
            $parentAdmin = $parentAdmin->getParent();
1582
        }
1583
1584
        if ($parentAdmin->getCode() === $child->getCode()) {
1585
            throw new \RuntimeException(sprintf(
1586
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1587
                $child->getCode(),
1588
                $this->getCode()
1589
            ));
1590
        }
1591
1592
        $this->children[$child->getCode()] = $child;
1593
1594
        $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...
1595
        $child->addParentAssociationMapping($this->getCode(), $field);
1596
    }
1597
1598
    public function hasChild(string $code): bool
1599
    {
1600
        return isset($this->children[$code]);
1601
    }
1602
1603
    public function getChildren(): array
1604
    {
1605
        return $this->children;
1606
    }
1607
1608
    public function getChild(string $code): AdminInterface
1609
    {
1610
        if (!$this->hasChild($code)) {
1611
            throw new \LogicException(sprintf(
1612
                'Admin "%s" has no child for the code %s.',
1613
                static::class,
1614
                $code
1615
            ));
1616
        }
1617
1618
        return $this->children[$code];
1619
    }
1620
1621
    public function setParent(AdminInterface $parent): void
1622
    {
1623
        $this->parent = $parent;
1624
    }
1625
1626
    public function getParent(): AdminInterface
1627
    {
1628
        if (!$this->isChild()) {
1629
            throw new \LogicException(sprintf(
1630
                'Admin "%s" has no parent.',
1631
                static::class
1632
            ));
1633
        }
1634
1635
        return $this->parent;
1636
    }
1637
1638
    final public function getRootAncestor(): AdminInterface
1639
    {
1640
        $parent = $this;
1641
1642
        while ($parent->isChild()) {
1643
            $parent = $parent->getParent();
1644
        }
1645
1646
        return $parent;
1647
    }
1648
1649
    final public function getChildDepth(): int
1650
    {
1651
        $parent = $this;
1652
        $depth = 0;
1653
1654
        while ($parent->isChild()) {
1655
            $parent = $parent->getParent();
1656
            ++$depth;
1657
        }
1658
1659
        return $depth;
1660
    }
1661
1662
    final public function getCurrentLeafChildAdmin(): ?AdminInterface
1663
    {
1664
        $child = $this->getCurrentChildAdmin();
1665
1666
        if (null === $child) {
1667
            return null;
1668
        }
1669
1670
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1671
            $child = $c;
1672
        }
1673
1674
        return $child;
1675
    }
1676
1677
    public function isChild(): bool
1678
    {
1679
        return $this->parent instanceof AdminInterface;
1680
    }
1681
1682
    /**
1683
     * Returns true if the admin has children, false otherwise.
1684
     */
1685
    public function hasChildren(): bool
1686
    {
1687
        return \count($this->children) > 0;
1688
    }
1689
1690
    public function setUniqid(string $uniqid): void
1691
    {
1692
        $this->uniqid = $uniqid;
1693
    }
1694
1695
    public function getUniqid(): string
1696
    {
1697
        if (!$this->uniqid) {
1698
            $this->uniqid = sprintf('s%s', uniqid());
1699
        }
1700
1701
        return $this->uniqid;
1702
    }
1703
1704
    /**
1705
     * {@inheritdoc}
1706
     */
1707
    public function getClassnameLabel(): string
1708
    {
1709
        return $this->classnameLabel;
1710
    }
1711
1712
    public function getPersistentParameters(): array
1713
    {
1714
        $parameters = [];
1715
1716
        foreach ($this->getExtensions() as $extension) {
1717
            $params = $extension->getPersistentParameters($this);
1718
1719
            $parameters = array_merge($parameters, $params);
1720
        }
1721
1722
        return $parameters;
1723
    }
1724
1725
    /**
1726
     * {@inheritdoc}
1727
     */
1728
    public function getPersistentParameter(string $name)
1729
    {
1730
        $parameters = $this->getPersistentParameters();
1731
1732
        return $parameters[$name] ?? null;
1733
    }
1734
1735
    public function setCurrentChild(bool $currentChild): void
1736
    {
1737
        $this->currentChild = $currentChild;
1738
    }
1739
1740
    public function isCurrentChild(): bool
1741
    {
1742
        return $this->currentChild;
1743
    }
1744
1745
    /**
1746
     * Returns the current child admin instance.
1747
     *
1748
     * @return AdminInterface|null the current child admin instance
1749
     */
1750
    public function getCurrentChildAdmin(): ?AdminInterface
1751
    {
1752
        foreach ($this->children as $children) {
1753
            if ($children->isCurrentChild()) {
1754
                return $children;
1755
            }
1756
        }
1757
1758
        return null;
1759
    }
1760
1761
    public function setTranslationDomain(string $translationDomain): void
1762
    {
1763
        $this->translationDomain = $translationDomain;
1764
    }
1765
1766
    public function getTranslationDomain(): string
1767
    {
1768
        return $this->translationDomain;
1769
    }
1770
1771
    /**
1772
     * {@inheritdoc}
1773
     *
1774
     * NEXT_MAJOR: remove this method
1775
     *
1776
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
1777
     */
1778
    public function setTranslator(TranslatorInterface $translator): void
1779
    {
1780
        $args = \func_get_args();
1781
        if (isset($args[1]) && $args[1]) {
1782
            @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...
1783
                'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
1784
                __METHOD__
1785
            ), E_USER_DEPRECATED);
1786
        }
1787
1788
        $this->translator = $translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since sonata-project/admin-bundle 3.9, to be removed with 4.0

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

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

Loading history...
1789
    }
1790
1791
    public function getTranslationLabel(string $label, string $context = '', string $type = ''): string
1792
    {
1793
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
1794
    }
1795
1796
    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...
1797
    {
1798
        $this->request = $request;
1799
1800
        foreach ($this->getChildren() as $children) {
1801
            $children->setRequest($request);
1802
        }
1803
    }
1804
1805
    public function getRequest(): Request
1806
    {
1807
        if (!$this->request) {
1808
            throw new \LogicException('The Request object has not been set');
1809
        }
1810
1811
        return $this->request;
1812
    }
1813
1814
    public function hasRequest(): bool
1815
    {
1816
        return null !== $this->request;
1817
    }
1818
1819
    public function setFormContractor(FormContractorInterface $formBuilder): void
1820
    {
1821
        $this->formContractor = $formBuilder;
1822
    }
1823
1824
    public function getFormContractor(): ?FormContractorInterface
1825
    {
1826
        return $this->formContractor;
1827
    }
1828
1829
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder): void
1830
    {
1831
        $this->datagridBuilder = $datagridBuilder;
1832
    }
1833
1834
    public function getDatagridBuilder(): ?DatagridBuilderInterface
1835
    {
1836
        return $this->datagridBuilder;
1837
    }
1838
1839
    public function setListBuilder(ListBuilderInterface $listBuilder): void
1840
    {
1841
        $this->listBuilder = $listBuilder;
1842
    }
1843
1844
    public function getListBuilder(): ?ListBuilderInterface
1845
    {
1846
        return $this->listBuilder;
1847
    }
1848
1849
    public function setShowBuilder(?ShowBuilderInterface $showBuilder): void
1850
    {
1851
        $this->showBuilder = $showBuilder;
1852
    }
1853
1854
    public function getShowBuilder(): ?ShowBuilderInterface
1855
    {
1856
        return $this->showBuilder;
1857
    }
1858
1859
    public function setConfigurationPool(Pool $configurationPool): void
1860
    {
1861
        $this->configurationPool = $configurationPool;
1862
    }
1863
1864
    public function getConfigurationPool(): ?Pool
1865
    {
1866
        return $this->configurationPool;
1867
    }
1868
1869
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator): void
1870
    {
1871
        $this->routeGenerator = $routeGenerator;
1872
    }
1873
1874
    public function getRouteGenerator(): ?RouteGeneratorInterface
1875
    {
1876
        return $this->routeGenerator;
1877
    }
1878
1879
    public function getCode(): string
1880
    {
1881
        return $this->code;
1882
    }
1883
1884
    public function getBaseCodeRoute(): string
1885
    {
1886
        if ($this->isChild()) {
1887
            return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
1888
        }
1889
1890
        return $this->getCode();
1891
    }
1892
1893
    public function getModelManager(): ?ModelManagerInterface
1894
    {
1895
        return $this->modelManager;
1896
    }
1897
1898
    public function setModelManager(?ModelManagerInterface $modelManager): void
1899
    {
1900
        $this->modelManager = $modelManager;
1901
    }
1902
1903
    public function getManagerType(): ?string
1904
    {
1905
        return $this->managerType;
1906
    }
1907
1908
    public function setManagerType(?string $type): void
1909
    {
1910
        $this->managerType = $type;
1911
    }
1912
1913
    public function getObjectIdentifier()
1914
    {
1915
        return $this->getCode();
1916
    }
1917
1918
    /**
1919
     * Set the roles and permissions per role.
1920
     */
1921
    public function setSecurityInformation(array $information): void
1922
    {
1923
        $this->securityInformation = $information;
1924
    }
1925
1926
    public function getSecurityInformation(): array
1927
    {
1928
        return $this->securityInformation;
1929
    }
1930
1931
    /**
1932
     * Return the list of permissions the user should have in order to display the admin.
1933
     */
1934
    public function getPermissionsShow(string $context): array
1935
    {
1936
        switch ($context) {
1937
            case self::CONTEXT_DASHBOARD:
1938
            case self::CONTEXT_MENU:
1939
            default:
1940
                return ['LIST'];
1941
        }
1942
    }
1943
1944
    public function showIn(string $context): bool
1945
    {
1946
        switch ($context) {
1947
            case self::CONTEXT_DASHBOARD:
1948
            case self::CONTEXT_MENU:
1949
            default:
1950
                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...
1951
        }
1952
    }
1953
1954
    public function createObjectSecurity(object $object): void
1955
    {
1956
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
1957
    }
1958
1959
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler): void
1960
    {
1961
        $this->securityHandler = $securityHandler;
1962
    }
1963
1964
    public function getSecurityHandler(): ?SecurityHandlerInterface
1965
    {
1966
        return $this->securityHandler;
1967
    }
1968
1969
    /**
1970
     * NEXT_MAJOR: Decide the type declaration for the $name argument, since it is
1971
     * passed as argument 1 for `SecurityHandlerInterface::isGranted()`, which
1972
     * accepts string and array.
1973
     */
1974
    public function isGranted($name, ?object $object = null): bool
1975
    {
1976
        $objectRef = $object ? sprintf('/%s#%s', spl_object_hash($object), $this->id($object)) : '';
1977
        $key = md5(json_encode($name).$objectRef);
1978
1979
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
1980
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
1981
        }
1982
1983
        return $this->cacheIsGranted[$key];
1984
    }
1985
1986
    /**
1987
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
1988
     * passed as argument 1 for `ModelManagerInterface::getUrlSafeIdentifier()`, which
1989
     * accepts null.
1990
     */
1991
    public function getUrlSafeIdentifier($model): ?string
1992
    {
1993
        return $this->getModelManager()->getUrlSafeIdentifier($model);
1994
    }
1995
1996
    /**
1997
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
1998
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
1999
     * accepts null.
2000
     */
2001
    public function getNormalizedIdentifier($model): ?string
2002
    {
2003
        return $this->getModelManager()->getNormalizedIdentifier($model);
2004
    }
2005
2006
    /**
2007
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
2008
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
2009
     * accepts null.
2010
     */
2011
    public function id($model): ?string
2012
    {
2013
        return $this->getNormalizedIdentifier($model);
2014
    }
2015
2016
    public function setValidator(ValidatorInterface $validator): void
2017
    {
2018
        $this->validator = $validator;
2019
    }
2020
2021
    public function getValidator(): ?ValidatorInterface
2022
    {
2023
        return $this->validator;
2024
    }
2025
2026
    public function getShow(): ?FieldDescriptionCollection
2027
    {
2028
        $this->buildShow();
2029
2030
        return $this->show;
2031
    }
2032
2033
    public function setFormTheme(array $formTheme): void
2034
    {
2035
        $this->formTheme = $formTheme;
2036
    }
2037
2038
    public function getFormTheme(): array
2039
    {
2040
        return $this->formTheme;
2041
    }
2042
2043
    public function setFilterTheme(array $filterTheme): void
2044
    {
2045
        $this->filterTheme = $filterTheme;
2046
    }
2047
2048
    public function getFilterTheme(): array
2049
    {
2050
        return $this->filterTheme;
2051
    }
2052
2053
    public function addExtension(AdminExtensionInterface $extension): void
2054
    {
2055
        $this->extensions[] = $extension;
2056
    }
2057
2058
    public function getExtensions(): array
2059
    {
2060
        return $this->extensions;
2061
    }
2062
2063
    public function setMenuFactory(FactoryInterface $menuFactory): void
2064
    {
2065
        $this->menuFactory = $menuFactory;
2066
    }
2067
2068
    public function getMenuFactory(): ?FactoryInterface
2069
    {
2070
        return $this->menuFactory;
2071
    }
2072
2073
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder): void
2074
    {
2075
        $this->routeBuilder = $routeBuilder;
2076
    }
2077
2078
    public function getRouteBuilder(): ?RouteBuilderInterface
2079
    {
2080
        return $this->routeBuilder;
2081
    }
2082
2083
    /**
2084
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since there
2085
     * are tests ensuring to accept null (`GetShortObjectDescriptionActionTest::testGetShortObjectDescriptionActionEmptyObjectIdAsJson()`).
2086
     */
2087
    public function toString($object): string
2088
    {
2089
        if (!\is_object($object)) {
2090
            return '';
2091
        }
2092
2093
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2094
            return (string) $object;
2095
        }
2096
2097
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2098
    }
2099
2100
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy): void
2101
    {
2102
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2103
    }
2104
2105
    public function getLabelTranslatorStrategy(): ?LabelTranslatorStrategyInterface
2106
    {
2107
        return $this->labelTranslatorStrategy;
2108
    }
2109
2110
    public function supportsPreviewMode(): bool
2111
    {
2112
        return $this->supportsPreviewMode;
2113
    }
2114
2115
    /**
2116
     * Returns predefined per page options.
2117
     */
2118
    public function getPerPageOptions(): array
2119
    {
2120
        $perPageOptions = $this->getModelManager()->getDefaultPerPageOptions($this->class);
2121
        $perPageOptions[] = $this->getMaxPerPage();
2122
2123
        $perPageOptions = array_unique($perPageOptions);
2124
        sort($perPageOptions);
2125
2126
        return $perPageOptions;
2127
    }
2128
2129
    /**
2130
     * Set pager type.
2131
     */
2132
    public function setPagerType(string $pagerType): void
2133
    {
2134
        $this->pagerType = $pagerType;
2135
    }
2136
2137
    /**
2138
     * Get pager type.
2139
     */
2140
    public function getPagerType(): string
2141
    {
2142
        return $this->pagerType;
2143
    }
2144
2145
    /**
2146
     * Returns true if the per page value is allowed, false otherwise.
2147
     */
2148
    public function determinedPerPageValue(int $perPage): bool
2149
    {
2150
        return \in_array($perPage, $this->getPerPageOptions(), true);
2151
    }
2152
2153
    public function isAclEnabled(): bool
2154
    {
2155
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2156
    }
2157
2158
    /**
2159
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since it is
2160
     * passed as argument 1 to `toString()` method, which currently accepts null.
2161
     */
2162
    public function getObjectMetadata($object): MetadataInterface
2163
    {
2164
        return new Metadata($this->toString($object));
2165
    }
2166
2167
    public function getListModes(): array
2168
    {
2169
        return $this->listModes;
2170
    }
2171
2172
    public function setListMode(string $mode): void
2173
    {
2174
        if (!$this->hasRequest()) {
2175
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2176
        }
2177
2178
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2179
    }
2180
2181
    public function getListMode(): string
2182
    {
2183
        if (!$this->hasRequest()) {
2184
            return 'list';
2185
        }
2186
2187
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2188
    }
2189
2190
    public function getAccessMapping(): array
2191
    {
2192
        return $this->accessMapping;
2193
    }
2194
2195
    public function checkAccess(string $action, ?object $object = null): void
2196
    {
2197
        $access = $this->getAccess();
2198
2199
        if (!\array_key_exists($action, $access)) {
2200
            throw new \InvalidArgumentException(sprintf(
2201
                'Action "%s" could not be found in access mapping.'
2202
                .' Please make sure your action is defined into your admin class accessMapping property.',
2203
                $action
2204
            ));
2205
        }
2206
2207
        if (!\is_array($access[$action])) {
2208
            $access[$action] = [$access[$action]];
2209
        }
2210
2211
        foreach ($access[$action] as $role) {
2212
            if (false === $this->isGranted($role, $object)) {
2213
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2214
            }
2215
        }
2216
    }
2217
2218
    /**
2219
     * {@inheritdoc}
2220
     */
2221
    public function hasAccess(string $action, ?object $object = null): bool
2222
    {
2223
        $access = $this->getAccess();
2224
2225
        if (!\array_key_exists($action, $access)) {
2226
            return false;
2227
        }
2228
2229
        if (!\is_array($access[$action])) {
2230
            $access[$action] = [$access[$action]];
2231
        }
2232
2233
        foreach ($access[$action] as $role) {
2234
            if (false === $this->isGranted($role, $object)) {
2235
                return false;
2236
            }
2237
        }
2238
2239
        return true;
2240
    }
2241
2242
    final public function getActionButtons(string $action, ?object $object = null): array
2243
    {
2244
        $buttonList = [];
2245
2246
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
2247
            && $this->hasAccess('create')
2248
            && $this->hasRoute('create')
2249
        ) {
2250
            $buttonList['create'] = [
2251
                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2252
            ];
2253
        }
2254
2255
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
2256
            && $this->canAccessObject('edit', $object)
2257
            && $this->hasRoute('edit')
2258
        ) {
2259
            $buttonList['edit'] = [
2260
                'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2261
            ];
2262
        }
2263
2264
        if (\in_array($action, ['show', 'edit', 'acl'], true)
2265
            && $this->canAccessObject('history', $object)
2266
            && $this->hasRoute('history')
2267
        ) {
2268
            $buttonList['history'] = [
2269
                'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2270
            ];
2271
        }
2272
2273
        if (\in_array($action, ['edit', 'history'], true)
2274
            && $this->isAclEnabled()
2275
            && $this->canAccessObject('acl', $object)
2276
            && $this->hasRoute('acl')
2277
        ) {
2278
            $buttonList['acl'] = [
2279
                'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2280
            ];
2281
        }
2282
2283
        if (\in_array($action, ['edit', 'history', 'acl'], true)
2284
            && $this->canAccessObject('show', $object)
2285
            && \count($this->getShow()) > 0
2286
            && $this->hasRoute('show')
2287
        ) {
2288
            $buttonList['show'] = [
2289
                'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2290
            ];
2291
        }
2292
2293
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
2294
            && $this->hasAccess('list')
2295
            && $this->hasRoute('list')
2296
        ) {
2297
            $buttonList['list'] = [
2298
                'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2299
            ];
2300
        }
2301
2302
        $buttonList = $this->configureActionButtons($buttonList, $action, $object);
2303
2304
        foreach ($this->getExtensions() as $extension) {
2305
            $buttonList = $extension->configureActionButtons($this, $buttonList, $action, $object);
2306
        }
2307
2308
        return $buttonList;
2309
    }
2310
2311
    /**
2312
     * {@inheritdoc}
2313
     */
2314
    public function getDashboardActions(): array
2315
    {
2316
        $actions = [];
2317
2318
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2319
            $actions['create'] = [
2320
                'label' => 'link_add',
2321
                'translation_domain' => 'SonataAdminBundle',
2322
                'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2323
                'url' => $this->generateUrl('create'),
2324
                'icon' => 'plus-circle',
2325
            ];
2326
        }
2327
2328
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2329
            $actions['list'] = [
2330
                'label' => 'link_list',
2331
                'translation_domain' => 'SonataAdminBundle',
2332
                'url' => $this->generateUrl('list'),
2333
                'icon' => 'list',
2334
            ];
2335
        }
2336
2337
        return $actions;
2338
    }
2339
2340
    /**
2341
     * {@inheritdoc}
2342
     */
2343
    final public function showMosaicButton($isShown): void
2344
    {
2345
        if ($isShown) {
2346
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
2347
        } else {
2348
            unset($this->listModes['mosaic']);
2349
        }
2350
    }
2351
2352
    final public function getSearchResultLink(object $object): ?string
2353
    {
2354
        foreach ($this->searchResultActions as $action) {
2355
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2356
                return $this->generateObjectUrl($action, $object);
2357
            }
2358
        }
2359
2360
        return null;
2361
    }
2362
2363
    /**
2364
     * Checks if a filter type is set to a default value.
2365
     */
2366
    final public function isDefaultFilter(string $name): bool
2367
    {
2368
        $filter = $this->getFilterParameters();
2369
        $default = $this->getDefaultFilterValues();
2370
2371
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
2372
            return false;
2373
        }
2374
2375
        return $filter[$name] === $default[$name];
2376
    }
2377
2378
    public function canAccessObject(string $action, ?object $object = null): bool
2379
    {
2380
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2381
    }
2382
2383
    public function configureActionButtons(array $buttonList, string $action, ?object $object = null): array
2384
    {
2385
        return $buttonList;
2386
    }
2387
2388
    /**
2389
     * Hook to run after initialization.
2390
     */
2391
    protected function configure(): void
2392
    {
2393
    }
2394
2395
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
2396
    {
2397
        return $query;
2398
    }
2399
2400
    /**
2401
     * urlize the given word.
2402
     *
2403
     * @param string $sep the separator
2404
     */
2405
    final protected function urlize(string $word, string $sep = '_'): string
2406
    {
2407
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
2408
    }
2409
2410
    final protected function getTemplateRegistry(): MutableTemplateRegistryInterface
2411
    {
2412
        return $this->templateRegistry;
2413
    }
2414
2415
    /**
2416
     * Returns a list of default sort values.
2417
     *
2418
     * @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...
2419
     */
2420
    final protected function getDefaultSortValues(): array
2421
    {
2422
        $defaultSortValues = [];
2423
2424
        $this->configureDefaultSortValues($defaultSortValues);
2425
2426
        foreach ($this->getExtensions() as $extension) {
2427
            $extension->configureDefaultSortValues($this, $defaultSortValues);
2428
        }
2429
2430
        return $defaultSortValues;
2431
    }
2432
2433
    /**
2434
     * Returns a list of default filters.
2435
     */
2436
    final protected function getDefaultFilterValues(): array
2437
    {
2438
        $defaultFilterValues = [];
2439
2440
        $this->configureDefaultFilterValues($defaultFilterValues);
2441
2442
        foreach ($this->getExtensions() as $extension) {
2443
            $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2444
        }
2445
2446
        return $defaultFilterValues;
2447
    }
2448
2449
    protected function configureFormFields(FormMapper $form): void
2450
    {
2451
    }
2452
2453
    protected function configureListFields(ListMapper $list): void
2454
    {
2455
    }
2456
2457
    protected function configureDatagridFilters(DatagridMapper $filter): void
2458
    {
2459
    }
2460
2461
    protected function configureShowFields(ShowMapper $show): void
2462
    {
2463
    }
2464
2465
    protected function configureRoutes(RouteCollectionInterface $collection): void
2466
    {
2467
    }
2468
2469
    /**
2470
     * Allows you to customize batch actions.
2471
     *
2472
     * @param array $actions List of actions
2473
     */
2474
    protected function configureBatchActions(array $actions): array
2475
    {
2476
        return $actions;
2477
    }
2478
2479
    /**
2480
     * Configures the tab menu in your admin.
2481
     */
2482
    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...
2483
    {
2484
    }
2485
2486
    /**
2487
     * build the view FieldDescription array.
2488
     */
2489
    protected function buildShow(): void
2490
    {
2491
        if ($this->loaded['show']) {
2492
            return;
2493
        }
2494
2495
        $this->loaded['show'] = true;
2496
2497
        $this->show = $this->getShowBuilder()->getBaseList();
2498
        $mapper = new ShowMapper($this->getShowBuilder(), $this->show, $this);
2499
2500
        $this->configureShowFields($mapper);
2501
2502
        foreach ($this->getExtensions() as $extension) {
2503
            $extension->configureShowFields($mapper);
2504
        }
2505
    }
2506
2507
    /**
2508
     * build the list FieldDescription array.
2509
     */
2510
    protected function buildList(): void
2511
    {
2512
        if ($this->loaded['list']) {
2513
            return;
2514
        }
2515
2516
        $this->loaded['list'] = true;
2517
2518
        $this->list = $this->getListBuilder()->getBaseList();
2519
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
2520
2521
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
2522
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2523
                $this->getClass(),
2524
                'batch',
2525
                [
2526
                    'label' => 'batch',
2527
                    'code' => '_batch',
2528
                    'sortable' => false,
2529
                    'virtual_field' => true,
2530
                ]
2531
            );
2532
2533
            $fieldDescription->setAdmin($this);
2534
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
2535
2536
            $mapper->add($fieldDescription, ListMapper::TYPE_BATCH);
2537
        }
2538
2539
        $this->configureListFields($mapper);
2540
2541
        foreach ($this->getExtensions() as $extension) {
2542
            $extension->configureListFields($mapper);
2543
        }
2544
2545
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
2546
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2547
                $this->getClass(),
2548
                'select',
2549
                [
2550
                    'label' => false,
2551
                    'code' => '_select',
2552
                    'sortable' => false,
2553
                    'virtual_field' => false,
2554
                ]
2555
            );
2556
2557
            $fieldDescription->setAdmin($this);
2558
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
2559
2560
            $mapper->add($fieldDescription, ListMapper::TYPE_SELECT);
2561
        }
2562
    }
2563
2564
    /**
2565
     * Build the form FieldDescription collection.
2566
     */
2567
    protected function buildForm(): void
2568
    {
2569
        if ($this->loaded['form']) {
2570
            return;
2571
        }
2572
2573
        $this->loaded['form'] = true;
2574
2575
        $formBuilder = $this->getFormBuilder();
2576
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
2577
            $this->preValidate($event->getData());
2578
        }, 100);
2579
2580
        $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...
2581
    }
2582
2583
    /**
2584
     * Gets the subclass corresponding to the given name.
2585
     *
2586
     * @param string $name The name of the sub class
2587
     *
2588
     * @return string the subclass
2589
     */
2590
    protected function getSubClass(string $name): string
2591
    {
2592
        if ($this->hasSubClass($name)) {
2593
            return $this->subClasses[$name];
2594
        }
2595
2596
        throw new \LogicException(sprintf('Unable to find the subclass `%s` for admin `%s`', $name, static::class));
2597
    }
2598
2599
    /**
2600
     * Attach the inline validator to the model metadata, this must be done once per admin.
2601
     */
2602
    protected function attachInlineValidator(): void
2603
    {
2604
        $admin = $this;
2605
2606
        // add the custom inline validation option
2607
        $metadata = $this->validator->getMetadataFor($this->getClass());
2608
        if (!$metadata instanceof GenericMetadata) {
2609
            throw new \UnexpectedValueException(
2610
                sprintf(
2611
                    'Cannot add inline validator for %s because its metadata is an instance of %s instead of %s',
2612
                    $this->getClass(),
2613
                    \get_class($metadata),
2614
                    GenericMetadata::class
2615
                )
2616
            );
2617
        }
2618
2619
        $metadata->addConstraint(new InlineConstraint([
2620
            'service' => $this,
2621
            'method' => static function (ErrorElement $errorElement, $object) use ($admin): void {
2622
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
2623
2624
                // This avoid the main validation to be cascaded to children
2625
                // The problem occurs when a model Page has a collection of Page as property
2626
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
2627
                    return;
2628
                }
2629
2630
                $admin->validate($errorElement, $object);
2631
2632
                foreach ($admin->getExtensions() as $extension) {
2633
                    $extension->validate($admin, $errorElement, $object);
2634
                }
2635
            },
2636
            'serializingWarning' => true,
2637
        ]));
2638
    }
2639
2640
    /**
2641
     * Return list routes with permissions name.
2642
     *
2643
     * @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...
2644
     */
2645
    protected function getAccess(): array
2646
    {
2647
        $access = array_merge([
2648
            'acl' => 'MASTER',
2649
            'export' => 'EXPORT',
2650
            'historyCompareRevisions' => 'EDIT',
2651
            'historyViewRevision' => 'EDIT',
2652
            'history' => 'EDIT',
2653
            'edit' => 'EDIT',
2654
            'show' => 'VIEW',
2655
            'create' => 'CREATE',
2656
            'delete' => 'DELETE',
2657
            'batchDelete' => 'DELETE',
2658
            'list' => 'LIST',
2659
        ], $this->getAccessMapping());
2660
2661
        foreach ($this->extensions as $extension) {
2662
            $access = array_merge($access, $extension->getAccessMapping($this));
2663
        }
2664
2665
        return $access;
2666
    }
2667
2668
    /**
2669
     * Configures a list of default filters.
2670
     */
2671
    protected function configureDefaultFilterValues(array &$filterValues): void
2672
    {
2673
    }
2674
2675
    /**
2676
     * Configures a list of default sort values.
2677
     *
2678
     * Example:
2679
     *   $sortValues['_sort_by'] = 'foo'
2680
     *   $sortValues['_sort_order'] = 'DESC'
2681
     */
2682
    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...
2683
    {
2684
    }
2685
2686
    /**
2687
     * Set the parent object, if any, to the provided object.
2688
     */
2689
    final protected function appendParentObject(object $object): void
2690
    {
2691
        if ($this->isChild() && $this->getParentAssociationMapping()) {
2692
            $parentAdmin = $this->getParent();
2693
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
2694
2695
            if (null !== $parentObject) {
2696
                $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
2697
                $propertyPath = new PropertyPath($this->getParentAssociationMapping());
2698
2699
                $value = $propertyAccessor->getValue($object, $propertyPath);
2700
2701
                if (\is_array($value) || $value instanceof \ArrayAccess) {
2702
                    $value[] = $parentObject;
2703
                    $propertyAccessor->setValue($object, $propertyPath, $value);
2704
                } else {
2705
                    $propertyAccessor->setValue($object, $propertyPath, $parentObject);
2706
                }
2707
            }
2708
        } elseif ($this->hasParentFieldDescription()) {
2709
            $parentAdmin = $this->getParentFieldDescription()->getAdmin();
2710
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
2711
2712
            if (null !== $parentObject) {
2713
                ObjectManipulator::setObject($object, $parentObject, $this->getParentFieldDescription());
2714
            }
2715
        }
2716
    }
2717
2718
    /**
2719
     * {@inheritdoc}
2720
     */
2721
    private function buildDatagrid(): void
2722
    {
2723
        if ($this->loaded['datagrid']) {
2724
            return;
2725
        }
2726
2727
        $this->loaded['datagrid'] = true;
2728
2729
        $filterParameters = $this->getFilterParameters();
2730
2731
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
2732
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
2733
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
2734
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
2735
            } else {
2736
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
2737
                    $this->getClass(),
2738
                    $filterParameters['_sort_by'],
2739
                    []
2740
                );
2741
2742
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
2743
            }
2744
        }
2745
2746
        // initialize the datagrid
2747
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
2748
2749
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
2750
2751
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
2752
2753
        // build the datagrid filter
2754
        $this->configureDatagridFilters($mapper);
2755
2756
        // ok, try to limit to add parent filter
2757
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
2758
            $mapper->add($this->getParentAssociationMapping(), null, [
2759
                'show_filter' => false,
2760
                'label' => false,
2761
                'field_type' => ModelHiddenType::class,
2762
                'field_options' => [
2763
                    'model_manager' => $this->getModelManager(),
2764
                ],
2765
                'operator_type' => HiddenType::class,
2766
            ], null, null, [
2767
                'admin_code' => $this->getParent()->getCode(),
2768
            ]);
2769
        }
2770
2771
        foreach ($this->getExtensions() as $extension) {
2772
            $extension->configureDatagridFilters($mapper);
2773
        }
2774
    }
2775
2776
    /**
2777
     * Build all the related urls to the current admin.
2778
     */
2779
    private function buildRoutes(): void
2780
    {
2781
        if ($this->loaded['routes']) {
2782
            return;
2783
        }
2784
2785
        $this->loaded['routes'] = true;
2786
2787
        $this->routes = new RouteCollection(
2788
            $this->getBaseCodeRoute(),
2789
            $this->getBaseRouteName(),
2790
            $this->getBaseRoutePattern(),
2791
            $this->getBaseControllerName()
2792
        );
2793
2794
        $this->routeBuilder->build($this, $this->routes);
2795
2796
        $this->configureRoutes($this->routes);
2797
2798
        foreach ($this->getExtensions() as $extension) {
2799
            $extension->configureRoutes($this, $this->routes);
2800
        }
2801
    }
2802
}
2803
2804
class_exists(ErrorElement::class);
2805