Completed
Pull Request — master (#6210)
by Jordi Sala
02:39
created

AbstractAdmin::getFilterParameters()   B

Complexity

Conditions 10
Paths 81

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

Loading history...
535
    {
536
    }
537
538
    /**
539
     * define custom variable.
540
     */
541
    public function initialize(): void
542
    {
543
        if (!$this->classnameLabel) {
544
            $this->classnameLabel = substr($this->getClass(), strrpos($this->getClass(), '\\') + 1);
545
        }
546
547
        $this->configure();
548
    }
549
550
    public function update(object $object): object
551
    {
552
        $this->preUpdate($object);
553
        foreach ($this->extensions as $extension) {
554
            $extension->preUpdate($this, $object);
555
        }
556
557
        $result = $this->getModelManager()->update($object);
558
        // BC compatibility
559
        if (null !== $result) {
560
            $object = $result;
561
        }
562
563
        $this->postUpdate($object);
564
        foreach ($this->extensions as $extension) {
565
            $extension->postUpdate($this, $object);
566
        }
567
568
        return $object;
569
    }
570
571
    public function create(object $object): object
572
    {
573
        $this->prePersist($object);
574
        foreach ($this->extensions as $extension) {
575
            $extension->prePersist($this, $object);
576
        }
577
578
        $result = $this->getModelManager()->create($object);
579
        // BC compatibility
580
        if (null !== $result) {
581
            $object = $result;
582
        }
583
584
        $this->postPersist($object);
585
        foreach ($this->extensions as $extension) {
586
            $extension->postPersist($this, $object);
587
        }
588
589
        $this->createObjectSecurity($object);
590
591
        return $object;
592
    }
593
594
    public function delete(object $object): void
595
    {
596
        $this->preRemove($object);
597
        foreach ($this->extensions as $extension) {
598
            $extension->preRemove($this, $object);
599
        }
600
601
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
602
        $this->getModelManager()->delete($object);
603
604
        $this->postRemove($object);
605
        foreach ($this->extensions as $extension) {
606
            $extension->postRemove($this, $object);
607
        }
608
    }
609
610
    public function preValidate(object $object): void
611
    {
612
    }
613
614
    public function preUpdate(object $object): void
615
    {
616
    }
617
618
    public function postUpdate(object $object): void
619
    {
620
    }
621
622
    public function prePersist(object $object): void
623
    {
624
    }
625
626
    public function postPersist(object $object): void
627
    {
628
    }
629
630
    public function preRemove(object $object): void
631
    {
632
    }
633
634
    public function postRemove(object $object): void
635
    {
636
    }
637
638
    public function preBatchAction(string $actionName, ProxyQueryInterface $query, array &$idx, bool $allElements = false): void
639
    {
640
    }
641
642
    public function getFilterParameters(): array
643
    {
644
        $parameters = [];
645
646
        // build the values array
647
        if ($this->hasRequest()) {
648
            $filters = $this->request->query->get('filter', []);
649
            if (isset($filters['_page'])) {
650
                $filters['_page'] = (int) $filters['_page'];
651
            }
652
            if (isset($filters['_per_page'])) {
653
                $filters['_per_page'] = (int) $filters['_per_page'];
654
            }
655
656
            // if filter persistence is configured
657
            if (null !== $this->filterPersister) {
658
                // if reset filters is asked, remove from storage
659
                if ('reset' === $this->request->query->get('filters')) {
660
                    $this->filterPersister->reset($this->getCode());
661
                }
662
663
                // if no filters, fetch from storage
664
                // otherwise save to storage
665
                if (empty($filters)) {
666
                    $filters = $this->filterPersister->get($this->getCode());
667
                } else {
668
                    $this->filterPersister->set($this->getCode(), $filters);
669
                }
670
            }
671
672
            $parameters = array_merge(
673
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
674
                $this->getDefaultSortValues(),
675
                $this->getDefaultFilterValues(),
676
                $filters
677
            );
678
679
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
680
                $parameters['_per_page'] = $this->getMaxPerPage();
681
            }
682
683
            // always force the parent value
684
            if ($this->isChild() && $this->getParentAssociationMapping()) {
685
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
686
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
687
            }
688
        }
689
690
        return $parameters;
691
    }
692
693
    /**
694
     * Returns the name of the parent related field, so the field can be use to set the default
695
     * value (ie the parent object) or to filter the object.
696
     *
697
     * @throws \InvalidArgumentException
698
     */
699
    public function getParentAssociationMapping(): ?string
700
    {
701
        if ($this->isChild()) {
702
            $parent = $this->getParent()->getCode();
703
704
            if (\array_key_exists($parent, $this->parentAssociationMapping)) {
705
                return $this->parentAssociationMapping[$parent];
706
            }
707
708
            throw new \InvalidArgumentException(sprintf(
709
                'There\'s no association between %s and %s.',
710
                $this->getCode(),
711
                $this->getParent()->getCode()
712
            ));
713
        }
714
715
        return null;
716
    }
717
718
    final public function addParentAssociationMapping(string $code, string $value): void
719
    {
720
        $this->parentAssociationMapping[$code] = $value;
721
    }
722
723
    /**
724
     * Returns the baseRoutePattern used to generate the routing information.
725
     *
726
     * @throws \RuntimeException
727
     *
728
     * @return string the baseRoutePattern used to generate the routing information
729
     */
730
    public function getBaseRoutePattern(): string
731
    {
732
        if (null !== $this->cachedBaseRoutePattern) {
733
            return $this->cachedBaseRoutePattern;
734
        }
735
736
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
737
            $baseRoutePattern = $this->baseRoutePattern;
738
            if (!$this->baseRoutePattern) {
739
                preg_match(self::CLASS_REGEX, $this->class, $matches);
740
741
                if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
742
                    throw new \RuntimeException(sprintf(
743
                        'Please define a default `baseRoutePattern` value for the admin class `%s`',
744
                        static::class
745
                    ));
746
                }
747
                $baseRoutePattern = $this->urlize($matches[5], '-');
748
            }
749
750
            $this->cachedBaseRoutePattern = sprintf(
751
                '%s/%s/%s',
752
                $this->getParent()->getBaseRoutePattern(),
753
                $this->getParent()->getRouterIdParameter(),
754
                $baseRoutePattern
755
            );
756
        } elseif ($this->baseRoutePattern) {
757
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
758
        } else {
759
            preg_match(self::CLASS_REGEX, $this->class, $matches);
760
761
            if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
762
                throw new \RuntimeException(sprintf(
763
                    'Please define a default `baseRoutePattern` value for the admin class `%s`',
764
                    static::class
765
                ));
766
            }
767
768
            $this->cachedBaseRoutePattern = sprintf(
769
                '/%s%s/%s',
770
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
771
                $this->urlize($matches[3], '-'),
772
                $this->urlize($matches[5], '-')
773
            );
774
        }
775
776
        return $this->cachedBaseRoutePattern;
777
    }
778
779
    /**
780
     * Returns the baseRouteName used to generate the routing information.
781
     *
782
     * @throws \RuntimeException
783
     *
784
     * @return string the baseRouteName used to generate the routing information
785
     */
786
    public function getBaseRouteName(): string
787
    {
788
        if (null !== $this->cachedBaseRouteName) {
789
            return $this->cachedBaseRouteName;
790
        }
791
792
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
793
            $baseRouteName = $this->baseRouteName;
794
            if (!$this->baseRouteName) {
795
                preg_match(self::CLASS_REGEX, $this->class, $matches);
796
797
                if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
798
                    throw new \RuntimeException(sprintf(
799
                        'Cannot automatically determine base route name,'
800
                        .' please define a default `baseRouteName` value for the admin class `%s`',
801
                        static::class
802
                    ));
803
                }
804
                $baseRouteName = $this->urlize($matches[5]);
805
            }
806
807
            $this->cachedBaseRouteName = sprintf(
808
                '%s_%s',
809
                $this->getParent()->getBaseRouteName(),
810
                $baseRouteName
811
            );
812
        } elseif ($this->baseRouteName) {
813
            $this->cachedBaseRouteName = $this->baseRouteName;
814
        } else {
815
            preg_match(self::CLASS_REGEX, $this->class, $matches);
816
817
            if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
818
                throw new \RuntimeException(sprintf(
819
                    'Cannot automatically determine base route name,'
820
                    .' please define a default `baseRouteName` value for the admin class `%s`',
821
                    static::class
822
                ));
823
            }
824
825
            $this->cachedBaseRouteName = sprintf(
826
                'admin_%s%s_%s',
827
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
828
                $this->urlize($matches[3]),
829
                $this->urlize($matches[5])
830
            );
831
        }
832
833
        return $this->cachedBaseRouteName;
834
    }
835
836
    public function getClass(): string
837
    {
838
        if ($this->hasActiveSubClass()) {
839
            if ($this->hasParentFieldDescription()) {
840
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
841
            }
842
843
            $subClass = $this->getRequest()->query->get('subclass');
844
845
            if (!$this->hasSubClass($subClass)) {
846
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
847
            }
848
849
            return $this->getSubClass($subClass);
850
        }
851
852
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
853
        if ($this->subject && \is_object($this->subject)) {
854
            return ClassUtils::getClass($this->subject);
855
        }
856
857
        return $this->class;
858
    }
859
860
    public function getSubClasses(): array
861
    {
862
        return $this->subClasses;
863
    }
864
865
    public function setSubClasses(array $subClasses): void
866
    {
867
        $this->subClasses = $subClasses;
868
    }
869
870
    public function hasSubClass(string $name): bool
871
    {
872
        return isset($this->subClasses[$name]);
873
    }
874
875
    public function hasActiveSubClass(): bool
876
    {
877
        if (\count($this->subClasses) > 0 && $this->request) {
878
            return null !== $this->getRequest()->query->get('subclass');
879
        }
880
881
        return false;
882
    }
883
884
    public function getActiveSubClass(): string
885
    {
886
        if (!$this->hasActiveSubClass()) {
887
            throw new \LogicException(sprintf(
888
                'Admin "%s" has no active subclass.',
889
                static::class
890
            ));
891
        }
892
893
        return $this->getSubClass($this->getActiveSubclassCode());
894
    }
895
896
    public function getActiveSubclassCode(): string
897
    {
898
        if (!$this->hasActiveSubClass()) {
899
            throw new \LogicException(sprintf(
900
                'Admin "%s" has no active subclass.',
901
                static::class
902
            ));
903
        }
904
905
        $subClass = $this->getRequest()->query->get('subclass');
906
907
        if (!$this->hasSubClass($subClass)) {
908
            throw new \LogicException(sprintf(
909
                'Admin "%s" has no active subclass.',
910
                static::class
911
            ));
912
        }
913
914
        return $subClass;
915
    }
916
917
    public function getBatchActions(): array
918
    {
919
        $actions = [];
920
921
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
922
            $actions['delete'] = [
923
                'label' => 'action_delete',
924
                'translation_domain' => 'SonataAdminBundle',
925
                'ask_confirmation' => true, // by default always true
926
            ];
927
        }
928
929
        $actions = $this->configureBatchActions($actions);
930
931
        foreach ($this->getExtensions() as $extension) {
932
            $actions = $extension->configureBatchActions($this, $actions);
933
        }
934
935
        foreach ($actions  as $name => &$action) {
936
            if (!\array_key_exists('label', $action)) {
937
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
938
            }
939
940
            if (!\array_key_exists('translation_domain', $action)) {
941
                $action['translation_domain'] = $this->getTranslationDomain();
942
            }
943
        }
944
945
        return $actions;
946
    }
947
948
    /**
949
     * NEXT_MAJOR: Create a `RouteCollectionInterface` and use as return type.
950
     */
951
    public function getRoutes(): RouteCollection
952
    {
953
        $this->buildRoutes();
954
955
        return $this->routes;
956
    }
957
958
    public function getRouterIdParameter(): string
959
    {
960
        return sprintf('{%s}', $this->getIdParameter());
961
    }
962
963
    public function getIdParameter(): string
964
    {
965
        $parameter = 'id';
966
967
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
968
            $parameter = sprintf('child%s', ucfirst($parameter));
969
        }
970
971
        return $parameter;
972
    }
973
974
    public function hasRoute(string $name): bool
975
    {
976
        if (!$this->routeGenerator) {
977
            throw new \RuntimeException('RouteGenerator cannot be null');
978
        }
979
980
        return $this->routeGenerator->hasAdminRoute($this, $name);
981
    }
982
983
    public function isCurrentRoute(string $name, ?string $adminCode = null): bool
984
    {
985
        if (!$this->hasRequest()) {
986
            return false;
987
        }
988
989
        $request = $this->getRequest();
990
        $route = $request->get('_route');
991
992
        if ($adminCode) {
993
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
994
        } else {
995
            $admin = $this;
996
        }
997
998
        if (!$admin) {
999
            return false;
1000
        }
1001
1002
        return sprintf('%s_%s', $admin->getBaseRouteName(), $name) === $route;
1003
    }
1004
1005
    public function generateObjectUrl(string $name, object $object, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1006
    {
1007
        $parameters['id'] = $this->getUrlSafeIdentifier($object);
1008
1009
        return $this->generateUrl($name, $parameters, $referenceType);
1010
    }
1011
1012
    public function generateUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1013
    {
1014
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $referenceType);
1015
    }
1016
1017
    public function generateMenuUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): array
1018
    {
1019
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $referenceType);
1020
    }
1021
1022
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry): void
1023
    {
1024
        $this->templateRegistry = $templateRegistry;
1025
    }
1026
1027
    /**
1028
     * @param array<string, string> $templates
1029
     */
1030
    public function setTemplates(array $templates): void
1031
    {
1032
        $this->getTemplateRegistry()->setTemplates($templates);
1033
    }
1034
1035
    /**
1036
     * {@inheritdoc}
1037
     */
1038
    public function setTemplate(string $name, string $template): void
1039
    {
1040
        $this->getTemplateRegistry()->setTemplate($name, $template);
1041
    }
1042
1043
    public function getNewInstance(): object
1044
    {
1045
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1046
1047
        $this->appendParentObject($object);
1048
1049
        foreach ($this->getExtensions() as $extension) {
1050
            $extension->alterNewInstance($this, $object);
1051
        }
1052
1053
        return $object;
1054
    }
1055
1056
    public function getFormBuilder(): FormBuilderInterface
1057
    {
1058
        $this->formOptions['data_class'] = $this->getClass();
1059
1060
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1061
            $this->getUniqid(),
1062
            $this->formOptions
1063
        );
1064
1065
        $this->defineFormBuilder($formBuilder);
1066
1067
        return $formBuilder;
1068
    }
1069
1070
    /**
1071
     * This method is being called by the main admin class and the child class,
1072
     * the getFormBuilder is only call by the main admin class.
1073
     */
1074
    public function defineFormBuilder(FormBuilderInterface $formBuilder): void
1075
    {
1076
        if (!$this->hasSubject()) {
1077
            throw new \LogicException(sprintf(
1078
                'Admin "%s" has no subject.',
1079
                static::class
1080
            ));
1081
        }
1082
1083
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1084
1085
        $this->configureFormFields($mapper);
1086
1087
        foreach ($this->getExtensions() as $extension) {
1088
            $extension->configureFormFields($mapper);
1089
        }
1090
1091
        $this->attachInlineValidator();
1092
    }
1093
1094
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription): void
1095
    {
1096
        $pool = $this->getConfigurationPool();
1097
1098
        $adminCode = $fieldDescription->getOption('admin_code');
1099
1100
        if (null !== $adminCode) {
1101
            if (!$pool->hasAdminByAdminCode($adminCode)) {
1102
                return;
1103
            }
1104
1105
            $admin = $pool->getAdminByAdminCode($adminCode);
1106
        } else {
1107
            $targetModel = $fieldDescription->getTargetModel();
1108
1109
            if (!$pool->hasAdminByClass($targetModel)) {
1110
                return;
1111
            }
1112
1113
            $admin = $pool->getAdminByClass($targetModel);
1114
        }
1115
1116
        if ($this->hasRequest()) {
1117
            $admin->setRequest($this->getRequest());
1118
        }
1119
1120
        $fieldDescription->setAssociationAdmin($admin);
1121
    }
1122
1123
    public function getObject($id): ?object
1124
    {
1125
        $object = $this->getModelManager()->find($this->getClass(), $id);
1126
        foreach ($this->getExtensions() as $extension) {
1127
            $extension->alterObject($this, $object);
0 ignored issues
show
Documentation introduced by
$object is of type object|null, but the function expects a object<Sonata\AdminBundle\Admin\object>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1362
        }
1363
1364
        $this->subject = $subject;
1365
    }
1366
1367
    public function getSubject(): object
1368
    {
1369
        if (!$this->hasSubject()) {
1370
            throw new \LogicException(sprintf(
1371
                'Admin "%s" has no subject.',
1372
                static::class
1373
            ));
1374
        }
1375
1376
        return $this->subject;
1377
    }
1378
1379
    public function hasSubject(): bool
1380
    {
1381
        if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
1382
            $id = $this->request->get($this->getIdParameter());
1383
1384
            if (null !== $id) {
1385
                $this->subject = $this->getObject($id);
1386
            }
1387
        }
1388
1389
        return null !== $this->subject;
1390
    }
1391
1392
    public function getFormFieldDescriptions(): array
1393
    {
1394
        $this->buildForm();
1395
1396
        return $this->formFieldDescriptions;
1397
    }
1398
1399
    public function getFormFieldDescription(string $name): ?FieldDescriptionInterface
1400
    {
1401
        $this->buildForm();
1402
1403
        if (!$this->hasFormFieldDescription($name)) {
1404
            throw new \LogicException(sprintf(
1405
                'Admin "%s" has no form field description for the field %s.',
1406
                static::class,
1407
                $name
1408
            ));
1409
        }
1410
1411
        return $this->formFieldDescriptions[$name];
1412
    }
1413
1414
    /**
1415
     * Returns true if the admin has a FieldDescription with the given $name.
1416
     */
1417
    public function hasFormFieldDescription(string $name): bool
1418
    {
1419
        $this->buildForm();
1420
1421
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1422
    }
1423
1424
    public function addFormFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1425
    {
1426
        $this->formFieldDescriptions[$name] = $fieldDescription;
1427
    }
1428
1429
    /**
1430
     * remove a FieldDescription.
1431
     */
1432
    public function removeFormFieldDescription(string $name): void
1433
    {
1434
        unset($this->formFieldDescriptions[$name]);
1435
    }
1436
1437
    /**
1438
     * build and return the collection of form FieldDescription.
1439
     *
1440
     * @return FieldDescriptionInterface[] collection of form FieldDescription
1441
     */
1442
    public function getShowFieldDescriptions(): array
1443
    {
1444
        $this->buildShow();
1445
1446
        return $this->showFieldDescriptions;
1447
    }
1448
1449
    /**
1450
     * Returns the form FieldDescription with the given $name.
1451
     */
1452
    public function getShowFieldDescription(string $name): FieldDescriptionInterface
1453
    {
1454
        $this->buildShow();
1455
1456
        if (!$this->hasShowFieldDescription($name)) {
1457
            throw new \LogicException(sprintf(
1458
                'Admin "%s" has no show field description for the field %s.',
1459
                static::class,
1460
                $name
1461
            ));
1462
        }
1463
1464
        return $this->showFieldDescriptions[$name];
1465
    }
1466
1467
    public function hasShowFieldDescription(string $name): bool
1468
    {
1469
        $this->buildShow();
1470
1471
        return \array_key_exists($name, $this->showFieldDescriptions);
1472
    }
1473
1474
    public function addShowFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1475
    {
1476
        $this->showFieldDescriptions[$name] = $fieldDescription;
1477
    }
1478
1479
    public function removeShowFieldDescription(string $name): void
1480
    {
1481
        unset($this->showFieldDescriptions[$name]);
1482
    }
1483
1484
    public function getListFieldDescriptions(): array
1485
    {
1486
        $this->buildList();
1487
1488
        return $this->listFieldDescriptions;
1489
    }
1490
1491
    public function getListFieldDescription(string $name): FieldDescriptionInterface
1492
    {
1493
        $this->buildList();
1494
1495
        if (!$this->hasListFieldDescription($name)) {
1496
            throw new \LogicException(sprintf(
1497
                'Admin "%s" has no list field description for %s.',
1498
                static::class,
1499
                $name
1500
            ));
1501
        }
1502
1503
        return $this->listFieldDescriptions[$name];
1504
    }
1505
1506
    public function hasListFieldDescription(string $name): bool
1507
    {
1508
        $this->buildList();
1509
1510
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1511
    }
1512
1513
    public function addListFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1514
    {
1515
        $this->listFieldDescriptions[$name] = $fieldDescription;
1516
    }
1517
1518
    public function removeListFieldDescription(string $name): void
1519
    {
1520
        unset($this->listFieldDescriptions[$name]);
1521
    }
1522
1523
    public function getFilterFieldDescription(string $name): FieldDescriptionInterface
1524
    {
1525
        $this->buildDatagrid();
1526
1527
        if (!$this->hasFilterFieldDescription($name)) {
1528
            throw new \LogicException(sprintf(
1529
                'Admin "%s" has no filter field description for the field %s.',
1530
                static::class,
1531
                $name
1532
            ));
1533
        }
1534
1535
        return $this->filterFieldDescriptions[$name];
1536
    }
1537
1538
    public function hasFilterFieldDescription(string $name): bool
1539
    {
1540
        $this->buildDatagrid();
1541
1542
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1543
    }
1544
1545
    public function addFilterFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1546
    {
1547
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1548
    }
1549
1550
    public function removeFilterFieldDescription(string $name): void
1551
    {
1552
        unset($this->filterFieldDescriptions[$name]);
1553
    }
1554
1555
    public function getFilterFieldDescriptions(): array
1556
    {
1557
        $this->buildDatagrid();
1558
1559
        return $this->filterFieldDescriptions;
1560
    }
1561
1562
    public function addChild(AdminInterface $child, string $field): void
1563
    {
1564
        $parentAdmin = $this;
1565
        while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
1566
            $parentAdmin = $parentAdmin->getParent();
1567
        }
1568
1569
        if ($parentAdmin->getCode() === $child->getCode()) {
1570
            throw new \RuntimeException(sprintf(
1571
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1572
                $child->getCode(),
1573
                $this->getCode()
1574
            ));
1575
        }
1576
1577
        $this->children[$child->getCode()] = $child;
1578
1579
        $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...
1580
        $child->addParentAssociationMapping($this->getCode(), $field);
1581
    }
1582
1583
    public function hasChild(string $code): bool
1584
    {
1585
        return isset($this->children[$code]);
1586
    }
1587
1588
    public function getChildren(): array
1589
    {
1590
        return $this->children;
1591
    }
1592
1593
    public function getChild(string $code): AdminInterface
1594
    {
1595
        if (!$this->hasChild($code)) {
1596
            throw new \LogicException(sprintf(
1597
                'Admin "%s" has no child for the code %s.',
1598
                static::class,
1599
                $code
1600
            ));
1601
        }
1602
1603
        return $this->children[$code];
1604
    }
1605
1606
    public function setParent(AdminInterface $parent): void
1607
    {
1608
        $this->parent = $parent;
1609
    }
1610
1611
    public function getParent(): AdminInterface
1612
    {
1613
        if (!$this->isChild()) {
1614
            throw new \LogicException(sprintf(
1615
                'Admin "%s" has no parent.',
1616
                static::class
1617
            ));
1618
        }
1619
1620
        return $this->parent;
1621
    }
1622
1623
    final public function getRootAncestor(): AdminInterface
1624
    {
1625
        $parent = $this;
1626
1627
        while ($parent->isChild()) {
1628
            $parent = $parent->getParent();
1629
        }
1630
1631
        return $parent;
1632
    }
1633
1634
    final public function getChildDepth(): int
1635
    {
1636
        $parent = $this;
1637
        $depth = 0;
1638
1639
        while ($parent->isChild()) {
1640
            $parent = $parent->getParent();
1641
            ++$depth;
1642
        }
1643
1644
        return $depth;
1645
    }
1646
1647
    final public function getCurrentLeafChildAdmin(): ?AdminInterface
1648
    {
1649
        $child = $this->getCurrentChildAdmin();
1650
1651
        if (null === $child) {
1652
            return null;
1653
        }
1654
1655
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1656
            $child = $c;
1657
        }
1658
1659
        return $child;
1660
    }
1661
1662
    public function isChild(): bool
1663
    {
1664
        return $this->parent instanceof AdminInterface;
1665
    }
1666
1667
    /**
1668
     * Returns true if the admin has children, false otherwise.
1669
     */
1670
    public function hasChildren(): bool
1671
    {
1672
        return \count($this->children) > 0;
1673
    }
1674
1675
    public function setUniqid(string $uniqid): void
1676
    {
1677
        $this->uniqid = $uniqid;
1678
    }
1679
1680
    public function getUniqid(): string
1681
    {
1682
        if (!$this->uniqid) {
1683
            $this->uniqid = sprintf('s%s', uniqid());
1684
        }
1685
1686
        return $this->uniqid;
1687
    }
1688
1689
    /**
1690
     * {@inheritdoc}
1691
     */
1692
    public function getClassnameLabel(): string
1693
    {
1694
        return $this->classnameLabel;
1695
    }
1696
1697
    public function getPersistentParameters(): array
1698
    {
1699
        $parameters = [];
1700
1701
        foreach ($this->getExtensions() as $extension) {
1702
            $params = $extension->getPersistentParameters($this);
1703
1704
            $parameters = array_merge($parameters, $params);
1705
        }
1706
1707
        return $parameters;
1708
    }
1709
1710
    /**
1711
     * {@inheritdoc}
1712
     */
1713
    public function getPersistentParameter(string $name)
1714
    {
1715
        $parameters = $this->getPersistentParameters();
1716
1717
        return $parameters[$name] ?? null;
1718
    }
1719
1720
    public function setCurrentChild(bool $currentChild): void
1721
    {
1722
        $this->currentChild = $currentChild;
1723
    }
1724
1725
    public function isCurrentChild(): bool
1726
    {
1727
        return $this->currentChild;
1728
    }
1729
1730
    /**
1731
     * Returns the current child admin instance.
1732
     *
1733
     * @return AdminInterface|null the current child admin instance
1734
     */
1735
    public function getCurrentChildAdmin(): ?AdminInterface
1736
    {
1737
        foreach ($this->children as $children) {
1738
            if ($children->isCurrentChild()) {
1739
                return $children;
1740
            }
1741
        }
1742
1743
        return null;
1744
    }
1745
1746
    public function setTranslationDomain(string $translationDomain): void
1747
    {
1748
        $this->translationDomain = $translationDomain;
1749
    }
1750
1751
    public function getTranslationDomain(): string
1752
    {
1753
        return $this->translationDomain;
1754
    }
1755
1756
    public function getTranslationLabel(string $label, string $context = '', string $type = ''): string
1757
    {
1758
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
1759
    }
1760
1761
    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...
1762
    {
1763
        $this->request = $request;
1764
1765
        foreach ($this->getChildren() as $children) {
1766
            $children->setRequest($request);
1767
        }
1768
    }
1769
1770
    public function getRequest(): Request
1771
    {
1772
        if (!$this->request) {
1773
            throw new \LogicException('The Request object has not been set');
1774
        }
1775
1776
        return $this->request;
1777
    }
1778
1779
    public function hasRequest(): bool
1780
    {
1781
        return null !== $this->request;
1782
    }
1783
1784
    public function setFormContractor(?FormContractorInterface $formBuilder): void
1785
    {
1786
        $this->formContractor = $formBuilder;
1787
    }
1788
1789
    public function getFormContractor(): ?FormContractorInterface
1790
    {
1791
        return $this->formContractor;
1792
    }
1793
1794
    public function setDatagridBuilder(?DatagridBuilderInterface $datagridBuilder): void
1795
    {
1796
        $this->datagridBuilder = $datagridBuilder;
1797
    }
1798
1799
    public function getDatagridBuilder(): ?DatagridBuilderInterface
1800
    {
1801
        return $this->datagridBuilder;
1802
    }
1803
1804
    public function setListBuilder(?ListBuilderInterface $listBuilder): void
1805
    {
1806
        $this->listBuilder = $listBuilder;
1807
    }
1808
1809
    public function getListBuilder(): ?ListBuilderInterface
1810
    {
1811
        return $this->listBuilder;
1812
    }
1813
1814
    public function setShowBuilder(?ShowBuilderInterface $showBuilder): void
1815
    {
1816
        $this->showBuilder = $showBuilder;
1817
    }
1818
1819
    public function getShowBuilder(): ?ShowBuilderInterface
1820
    {
1821
        return $this->showBuilder;
1822
    }
1823
1824
    public function setConfigurationPool(?Pool $configurationPool): void
1825
    {
1826
        $this->configurationPool = $configurationPool;
1827
    }
1828
1829
    public function getConfigurationPool(): ?Pool
1830
    {
1831
        return $this->configurationPool;
1832
    }
1833
1834
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator): void
1835
    {
1836
        $this->routeGenerator = $routeGenerator;
1837
    }
1838
1839
    public function getRouteGenerator(): ?RouteGeneratorInterface
1840
    {
1841
        return $this->routeGenerator;
1842
    }
1843
1844
    public function getCode(): string
1845
    {
1846
        return $this->code;
1847
    }
1848
1849
    public function getBaseCodeRoute(): string
1850
    {
1851
        if ($this->isChild()) {
1852
            return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
1853
        }
1854
1855
        return $this->getCode();
1856
    }
1857
1858
    public function getModelManager(): ?ModelManagerInterface
1859
    {
1860
        return $this->modelManager;
1861
    }
1862
1863
    public function setModelManager(?ModelManagerInterface $modelManager): void
1864
    {
1865
        $this->modelManager = $modelManager;
1866
    }
1867
1868
    public function getManagerType(): ?string
1869
    {
1870
        return $this->managerType;
1871
    }
1872
1873
    public function setManagerType(?string $type): void
1874
    {
1875
        $this->managerType = $type;
1876
    }
1877
1878
    public function getObjectIdentifier()
1879
    {
1880
        return $this->getCode();
1881
    }
1882
1883
    /**
1884
     * Set the roles and permissions per role.
1885
     */
1886
    public function setSecurityInformation(array $information): void
1887
    {
1888
        $this->securityInformation = $information;
1889
    }
1890
1891
    public function getSecurityInformation(): array
1892
    {
1893
        return $this->securityInformation;
1894
    }
1895
1896
    /**
1897
     * Return the list of permissions the user should have in order to display the admin.
1898
     */
1899
    public function getPermissionsShow(string $context): array
1900
    {
1901
        switch ($context) {
1902
            case self::CONTEXT_DASHBOARD:
1903
            case self::CONTEXT_MENU:
1904
            default:
1905
                return ['LIST'];
1906
        }
1907
    }
1908
1909
    public function showIn(string $context): bool
1910
    {
1911
        switch ($context) {
1912
            case self::CONTEXT_DASHBOARD:
1913
            case self::CONTEXT_MENU:
1914
            default:
1915
                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...
1916
        }
1917
    }
1918
1919
    public function createObjectSecurity(object $object): void
1920
    {
1921
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
1922
    }
1923
1924
    public function setSecurityHandler(?SecurityHandlerInterface $securityHandler): void
1925
    {
1926
        $this->securityHandler = $securityHandler;
1927
    }
1928
1929
    public function getSecurityHandler(): ?SecurityHandlerInterface
1930
    {
1931
        return $this->securityHandler;
1932
    }
1933
1934
    /**
1935
     * NEXT_MAJOR: Decide the type declaration for the $name argument, since it is
1936
     * passed as argument 1 for `SecurityHandlerInterface::isGranted()`, which
1937
     * accepts string and array.
1938
     */
1939
    public function isGranted($name, ?object $object = null): bool
1940
    {
1941
        $objectRef = $object ? sprintf('/%s#%s', spl_object_hash($object), $this->id($object)) : '';
1942
        $key = md5(json_encode($name).$objectRef);
1943
1944
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
1945
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
1946
        }
1947
1948
        return $this->cacheIsGranted[$key];
1949
    }
1950
1951
    /**
1952
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
1953
     * passed as argument 1 for `ModelManagerInterface::getUrlSafeIdentifier()`, which
1954
     * accepts null.
1955
     */
1956
    public function getUrlSafeIdentifier($model): ?string
1957
    {
1958
        return $this->getModelManager()->getUrlSafeIdentifier($model);
1959
    }
1960
1961
    /**
1962
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
1963
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
1964
     * accepts null.
1965
     */
1966
    public function getNormalizedIdentifier($model): ?string
1967
    {
1968
        return $this->getModelManager()->getNormalizedIdentifier($model);
1969
    }
1970
1971
    /**
1972
     * NEXT_MAJOR: Decide the type declaration for the $model argument, since it is
1973
     * passed as argument 1 for `ModelManagerInterface::getNormalizedIdentifier()`, which
1974
     * accepts null.
1975
     */
1976
    public function id($model): ?string
1977
    {
1978
        return $this->getNormalizedIdentifier($model);
1979
    }
1980
1981
    public function setValidator(?ValidatorInterface $validator): void
1982
    {
1983
        $this->validator = $validator;
1984
    }
1985
1986
    public function getValidator(): ?ValidatorInterface
1987
    {
1988
        return $this->validator;
1989
    }
1990
1991
    public function getShow(): ?FieldDescriptionCollection
1992
    {
1993
        $this->buildShow();
1994
1995
        return $this->show;
1996
    }
1997
1998
    public function setFormTheme(array $formTheme): void
1999
    {
2000
        $this->formTheme = $formTheme;
2001
    }
2002
2003
    public function getFormTheme(): array
2004
    {
2005
        return $this->formTheme;
2006
    }
2007
2008
    public function setFilterTheme(array $filterTheme): void
2009
    {
2010
        $this->filterTheme = $filterTheme;
2011
    }
2012
2013
    public function getFilterTheme(): array
2014
    {
2015
        return $this->filterTheme;
2016
    }
2017
2018
    public function addExtension(AdminExtensionInterface $extension): void
2019
    {
2020
        $this->extensions[] = $extension;
2021
    }
2022
2023
    public function getExtensions(): array
2024
    {
2025
        return $this->extensions;
2026
    }
2027
2028
    public function setMenuFactory(?FactoryInterface $menuFactory): void
2029
    {
2030
        $this->menuFactory = $menuFactory;
2031
    }
2032
2033
    public function getMenuFactory(): ?FactoryInterface
2034
    {
2035
        return $this->menuFactory;
2036
    }
2037
2038
    public function setRouteBuilder(?RouteBuilderInterface $routeBuilder): void
2039
    {
2040
        $this->routeBuilder = $routeBuilder;
2041
    }
2042
2043
    public function getRouteBuilder(): ?RouteBuilderInterface
2044
    {
2045
        return $this->routeBuilder;
2046
    }
2047
2048
    /**
2049
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since there
2050
     * are tests ensuring to accept null (`GetShortObjectDescriptionActionTest::testGetShortObjectDescriptionActionEmptyObjectIdAsJson()`).
2051
     */
2052
    public function toString($object): string
2053
    {
2054
        if (!\is_object($object)) {
2055
            return '';
2056
        }
2057
2058
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2059
            return (string) $object;
2060
        }
2061
2062
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2063
    }
2064
2065
    public function setLabelTranslatorStrategy(?LabelTranslatorStrategyInterface $labelTranslatorStrategy): void
2066
    {
2067
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2068
    }
2069
2070
    public function getLabelTranslatorStrategy(): ?LabelTranslatorStrategyInterface
2071
    {
2072
        return $this->labelTranslatorStrategy;
2073
    }
2074
2075
    public function supportsPreviewMode(): bool
2076
    {
2077
        return $this->supportsPreviewMode;
2078
    }
2079
2080
    /**
2081
     * Returns predefined per page options.
2082
     */
2083
    public function getPerPageOptions(): array
2084
    {
2085
        $perPageOptions = $this->getModelManager()->getDefaultPerPageOptions($this->class);
2086
        $perPageOptions[] = $this->getMaxPerPage();
2087
2088
        $perPageOptions = array_unique($perPageOptions);
2089
        sort($perPageOptions);
2090
2091
        return $perPageOptions;
2092
    }
2093
2094
    /**
2095
     * Set pager type.
2096
     */
2097
    public function setPagerType(string $pagerType): void
2098
    {
2099
        $this->pagerType = $pagerType;
2100
    }
2101
2102
    /**
2103
     * Get pager type.
2104
     */
2105
    public function getPagerType(): string
2106
    {
2107
        return $this->pagerType;
2108
    }
2109
2110
    /**
2111
     * Returns true if the per page value is allowed, false otherwise.
2112
     */
2113
    public function determinedPerPageValue(int $perPage): bool
2114
    {
2115
        return \in_array($perPage, $this->getPerPageOptions(), true);
2116
    }
2117
2118
    public function isAclEnabled(): bool
2119
    {
2120
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2121
    }
2122
2123
    /**
2124
     * NEXT_MAJOR: Decide the type declaration for the $object argument, since it is
2125
     * passed as argument 1 to `toString()` method, which currently accepts null.
2126
     */
2127
    public function getObjectMetadata($object): MetadataInterface
2128
    {
2129
        return new Metadata($this->toString($object));
2130
    }
2131
2132
    public function getListModes(): array
2133
    {
2134
        return $this->listModes;
2135
    }
2136
2137
    public function setListMode(string $mode): void
2138
    {
2139
        if (!$this->hasRequest()) {
2140
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2141
        }
2142
2143
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2144
    }
2145
2146
    public function getListMode(): string
2147
    {
2148
        if (!$this->hasRequest()) {
2149
            return 'list';
2150
        }
2151
2152
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2153
    }
2154
2155
    public function getAccessMapping(): array
2156
    {
2157
        return $this->accessMapping;
2158
    }
2159
2160
    public function checkAccess(string $action, ?object $object = null): void
2161
    {
2162
        $access = $this->getAccess();
2163
2164
        if (!\array_key_exists($action, $access)) {
2165
            throw new \InvalidArgumentException(sprintf(
2166
                'Action "%s" could not be found in access mapping.'
2167
                .' Please make sure your action is defined into your admin class accessMapping property.',
2168
                $action
2169
            ));
2170
        }
2171
2172
        if (!\is_array($access[$action])) {
2173
            $access[$action] = [$access[$action]];
2174
        }
2175
2176
        foreach ($access[$action] as $role) {
2177
            if (false === $this->isGranted($role, $object)) {
2178
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2179
            }
2180
        }
2181
    }
2182
2183
    /**
2184
     * {@inheritdoc}
2185
     */
2186
    public function hasAccess(string $action, ?object $object = null): bool
2187
    {
2188
        $access = $this->getAccess();
2189
2190
        if (!\array_key_exists($action, $access)) {
2191
            return false;
2192
        }
2193
2194
        if (!\is_array($access[$action])) {
2195
            $access[$action] = [$access[$action]];
2196
        }
2197
2198
        foreach ($access[$action] as $role) {
2199
            if (false === $this->isGranted($role, $object)) {
2200
                return false;
2201
            }
2202
        }
2203
2204
        return true;
2205
    }
2206
2207
    final public function getActionButtons(string $action, ?object $object = null): array
2208
    {
2209
        $buttonList = [];
2210
2211
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
2212
            && $this->hasAccess('create')
2213
            && $this->hasRoute('create')
2214
        ) {
2215
            $buttonList['create'] = [
2216
                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2217
            ];
2218
        }
2219
2220
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
2221
            && $this->canAccessObject('edit', $object)
2222
            && $this->hasRoute('edit')
2223
        ) {
2224
            $buttonList['edit'] = [
2225
                'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2226
            ];
2227
        }
2228
2229
        if (\in_array($action, ['show', 'edit', 'acl'], true)
2230
            && $this->canAccessObject('history', $object)
2231
            && $this->hasRoute('history')
2232
        ) {
2233
            $buttonList['history'] = [
2234
                'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2235
            ];
2236
        }
2237
2238
        if (\in_array($action, ['edit', 'history'], true)
2239
            && $this->isAclEnabled()
2240
            && $this->canAccessObject('acl', $object)
2241
            && $this->hasRoute('acl')
2242
        ) {
2243
            $buttonList['acl'] = [
2244
                'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2245
            ];
2246
        }
2247
2248
        if (\in_array($action, ['edit', 'history', 'acl'], true)
2249
            && $this->canAccessObject('show', $object)
2250
            && \count($this->getShow()) > 0
2251
            && $this->hasRoute('show')
2252
        ) {
2253
            $buttonList['show'] = [
2254
                'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2255
            ];
2256
        }
2257
2258
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
2259
            && $this->hasAccess('list')
2260
            && $this->hasRoute('list')
2261
        ) {
2262
            $buttonList['list'] = [
2263
                'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2264
            ];
2265
        }
2266
2267
        $buttonList = $this->configureActionButtons($buttonList, $action, $object);
2268
2269
        foreach ($this->getExtensions() as $extension) {
2270
            $buttonList = $extension->configureActionButtons($this, $buttonList, $action, $object);
2271
        }
2272
2273
        return $buttonList;
2274
    }
2275
2276
    /**
2277
     * {@inheritdoc}
2278
     */
2279
    public function getDashboardActions(): array
2280
    {
2281
        $actions = [];
2282
2283
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2284
            $actions['create'] = [
2285
                'label' => 'link_add',
2286
                'translation_domain' => 'SonataAdminBundle',
2287
                'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2288
                'url' => $this->generateUrl('create'),
2289
                'icon' => 'plus-circle',
2290
            ];
2291
        }
2292
2293
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2294
            $actions['list'] = [
2295
                'label' => 'link_list',
2296
                'translation_domain' => 'SonataAdminBundle',
2297
                'url' => $this->generateUrl('list'),
2298
                'icon' => 'list',
2299
            ];
2300
        }
2301
2302
        return $actions;
2303
    }
2304
2305
    /**
2306
     * {@inheritdoc}
2307
     */
2308
    final public function showMosaicButton($isShown): void
2309
    {
2310
        if ($isShown) {
2311
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
2312
        } else {
2313
            unset($this->listModes['mosaic']);
2314
        }
2315
    }
2316
2317
    final public function getSearchResultLink(object $object): ?string
2318
    {
2319
        foreach ($this->searchResultActions as $action) {
2320
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2321
                return $this->generateObjectUrl($action, $object);
2322
            }
2323
        }
2324
2325
        return null;
2326
    }
2327
2328
    /**
2329
     * Checks if a filter type is set to a default value.
2330
     */
2331
    final public function isDefaultFilter(string $name): bool
2332
    {
2333
        $filter = $this->getFilterParameters();
2334
        $default = $this->getDefaultFilterValues();
2335
2336
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
2337
            return false;
2338
        }
2339
2340
        return $filter[$name] === $default[$name];
2341
    }
2342
2343
    public function canAccessObject(string $action, ?object $object = null): bool
2344
    {
2345
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2346
    }
2347
2348
    public function configureActionButtons(array $buttonList, string $action, ?object $object = null): array
2349
    {
2350
        return $buttonList;
2351
    }
2352
2353
    /**
2354
     * Hook to run after initialization.
2355
     */
2356
    protected function configure(): void
2357
    {
2358
    }
2359
2360
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
2361
    {
2362
        return $query;
2363
    }
2364
2365
    /**
2366
     * urlize the given word.
2367
     *
2368
     * @param string $sep the separator
2369
     */
2370
    final protected function urlize(string $word, string $sep = '_'): string
2371
    {
2372
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
2373
    }
2374
2375
    final protected function getTemplateRegistry(): MutableTemplateRegistryInterface
2376
    {
2377
        return $this->templateRegistry;
2378
    }
2379
2380
    /**
2381
     * Returns a list of default sort values.
2382
     *
2383
     * @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...
2384
     */
2385
    final protected function getDefaultSortValues(): array
2386
    {
2387
        $defaultSortValues = [];
2388
2389
        $this->configureDefaultSortValues($defaultSortValues);
2390
2391
        foreach ($this->getExtensions() as $extension) {
2392
            $extension->configureDefaultSortValues($this, $defaultSortValues);
2393
        }
2394
2395
        return $defaultSortValues;
2396
    }
2397
2398
    /**
2399
     * Returns a list of default filters.
2400
     */
2401
    final protected function getDefaultFilterValues(): array
2402
    {
2403
        $defaultFilterValues = [];
2404
2405
        $this->configureDefaultFilterValues($defaultFilterValues);
2406
2407
        foreach ($this->getExtensions() as $extension) {
2408
            $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2409
        }
2410
2411
        return $defaultFilterValues;
2412
    }
2413
2414
    protected function configureFormFields(FormMapper $form): void
2415
    {
2416
    }
2417
2418
    protected function configureListFields(ListMapper $list): void
2419
    {
2420
    }
2421
2422
    protected function configureDatagridFilters(DatagridMapper $filter): void
2423
    {
2424
    }
2425
2426
    protected function configureShowFields(ShowMapper $show): void
2427
    {
2428
    }
2429
2430
    protected function configureRoutes(RouteCollection $collection): void
2431
    {
2432
    }
2433
2434
    /**
2435
     * Allows you to customize batch actions.
2436
     *
2437
     * @param array $actions List of actions
2438
     */
2439
    protected function configureBatchActions(array $actions): array
2440
    {
2441
        return $actions;
2442
    }
2443
2444
    /**
2445
     * Configures the tab menu in your admin.
2446
     */
2447
    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...
2448
    {
2449
    }
2450
2451
    /**
2452
     * build the view FieldDescription array.
2453
     */
2454
    protected function buildShow(): void
2455
    {
2456
        if ($this->loaded['show']) {
2457
            return;
2458
        }
2459
2460
        $this->loaded['show'] = true;
2461
2462
        $this->show = $this->getShowBuilder()->getBaseList();
2463
        $mapper = new ShowMapper($this->getShowBuilder(), $this->show, $this);
2464
2465
        $this->configureShowFields($mapper);
2466
2467
        foreach ($this->getExtensions() as $extension) {
2468
            $extension->configureShowFields($mapper);
2469
        }
2470
    }
2471
2472
    /**
2473
     * build the list FieldDescription array.
2474
     */
2475
    protected function buildList(): void
2476
    {
2477
        if ($this->loaded['list']) {
2478
            return;
2479
        }
2480
2481
        $this->loaded['list'] = true;
2482
2483
        $this->list = $this->getListBuilder()->getBaseList();
2484
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
2485
2486
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
2487
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2488
                $this->getClass(),
2489
                'batch',
2490
                [
2491
                    'label' => 'batch',
2492
                    'code' => '_batch',
2493
                    'sortable' => false,
2494
                    'virtual_field' => true,
2495
                ]
2496
            );
2497
2498
            $fieldDescription->setAdmin($this);
2499
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
2500
2501
            $mapper->add($fieldDescription, ListMapper::TYPE_BATCH);
2502
        }
2503
2504
        $this->configureListFields($mapper);
2505
2506
        foreach ($this->getExtensions() as $extension) {
2507
            $extension->configureListFields($mapper);
2508
        }
2509
2510
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
2511
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2512
                $this->getClass(),
2513
                'select',
2514
                [
2515
                    'label' => false,
2516
                    'code' => '_select',
2517
                    'sortable' => false,
2518
                    'virtual_field' => false,
2519
                ]
2520
            );
2521
2522
            $fieldDescription->setAdmin($this);
2523
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
2524
2525
            $mapper->add($fieldDescription, ListMapper::TYPE_SELECT);
2526
        }
2527
    }
2528
2529
    /**
2530
     * Build the form FieldDescription collection.
2531
     */
2532
    protected function buildForm(): void
2533
    {
2534
        if ($this->loaded['form']) {
2535
            return;
2536
        }
2537
2538
        $this->loaded['form'] = true;
2539
2540
        $formBuilder = $this->getFormBuilder();
2541
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
2542
            $this->preValidate($event->getData());
2543
        }, 100);
2544
2545
        $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...
2546
    }
2547
2548
    /**
2549
     * Gets the subclass corresponding to the given name.
2550
     *
2551
     * @param string $name The name of the sub class
2552
     *
2553
     * @return string the subclass
2554
     */
2555
    protected function getSubClass(string $name): string
2556
    {
2557
        if ($this->hasSubClass($name)) {
2558
            return $this->subClasses[$name];
2559
        }
2560
2561
        throw new \LogicException(sprintf('Unable to find the subclass `%s` for admin `%s`', $name, static::class));
2562
    }
2563
2564
    /**
2565
     * Attach the inline validator to the model metadata, this must be done once per admin.
2566
     */
2567
    protected function attachInlineValidator(): void
2568
    {
2569
        $admin = $this;
2570
2571
        // add the custom inline validation option
2572
        $metadata = $this->validator->getMetadataFor($this->getClass());
2573
        if (!$metadata instanceof GenericMetadata) {
2574
            throw new \UnexpectedValueException(
2575
                sprintf(
2576
                    'Cannot add inline validator for %s because its metadata is an instance of %s instead of %s',
2577
                    $this->getClass(),
2578
                    \get_class($metadata),
2579
                    GenericMetadata::class
2580
                )
2581
            );
2582
        }
2583
2584
        $metadata->addConstraint(new InlineConstraint([
2585
            'service' => $this,
2586
            'method' => static function (ErrorElement $errorElement, $object) use ($admin): void {
2587
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
2588
2589
                // This avoid the main validation to be cascaded to children
2590
                // The problem occurs when a model Page has a collection of Page as property
2591
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
2592
                    return;
2593
                }
2594
2595
                $admin->validate($errorElement, $object);
2596
2597
                foreach ($admin->getExtensions() as $extension) {
2598
                    $extension->validate($admin, $errorElement, $object);
2599
                }
2600
            },
2601
            'serializingWarning' => true,
2602
        ]));
2603
    }
2604
2605
    /**
2606
     * Return list routes with permissions name.
2607
     *
2608
     * @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...
2609
     */
2610
    protected function getAccess(): array
2611
    {
2612
        $access = array_merge([
2613
            'acl' => 'MASTER',
2614
            'export' => 'EXPORT',
2615
            'historyCompareRevisions' => 'EDIT',
2616
            'historyViewRevision' => 'EDIT',
2617
            'history' => 'EDIT',
2618
            'edit' => 'EDIT',
2619
            'show' => 'VIEW',
2620
            'create' => 'CREATE',
2621
            'delete' => 'DELETE',
2622
            'batchDelete' => 'DELETE',
2623
            'list' => 'LIST',
2624
        ], $this->getAccessMapping());
2625
2626
        foreach ($this->extensions as $extension) {
2627
            $access = array_merge($access, $extension->getAccessMapping($this));
2628
        }
2629
2630
        return $access;
2631
    }
2632
2633
    /**
2634
     * Configures a list of default filters.
2635
     */
2636
    protected function configureDefaultFilterValues(array &$filterValues): void
2637
    {
2638
    }
2639
2640
    /**
2641
     * Configures a list of default sort values.
2642
     *
2643
     * Example:
2644
     *   $sortValues['_sort_by'] = 'foo'
2645
     *   $sortValues['_sort_order'] = 'DESC'
2646
     */
2647
    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...
2648
    {
2649
    }
2650
2651
    /**
2652
     * Set the parent object, if any, to the provided object.
2653
     */
2654
    final protected function appendParentObject(object $object): void
2655
    {
2656
        if ($this->isChild() && $this->getParentAssociationMapping()) {
2657
            $parentAdmin = $this->getParent();
2658
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
2659
2660
            if (null !== $parentObject) {
2661
                $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
2662
                $propertyPath = new PropertyPath($this->getParentAssociationMapping());
2663
2664
                $value = $propertyAccessor->getValue($object, $propertyPath);
2665
2666
                if (\is_array($value) || $value instanceof \ArrayAccess) {
2667
                    $value[] = $parentObject;
2668
                    $propertyAccessor->setValue($object, $propertyPath, $value);
2669
                } else {
2670
                    $propertyAccessor->setValue($object, $propertyPath, $parentObject);
2671
                }
2672
            }
2673
        } elseif ($this->hasParentFieldDescription()) {
2674
            $parentAdmin = $this->getParentFieldDescription()->getAdmin();
2675
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
2676
2677
            if (null !== $parentObject) {
2678
                ObjectManipulator::setObject($object, $parentObject, $this->getParentFieldDescription());
2679
            }
2680
        }
2681
    }
2682
2683
    /**
2684
     * {@inheritdoc}
2685
     */
2686
    private function buildDatagrid(): void
2687
    {
2688
        if ($this->loaded['datagrid']) {
2689
            return;
2690
        }
2691
2692
        $this->loaded['datagrid'] = true;
2693
2694
        $filterParameters = $this->getFilterParameters();
2695
2696
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
2697
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
2698
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
2699
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
2700
            } else {
2701
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
2702
                    $this->getClass(),
2703
                    $filterParameters['_sort_by'],
2704
                    []
2705
                );
2706
2707
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
2708
            }
2709
        }
2710
2711
        // initialize the datagrid
2712
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
2713
2714
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
2715
2716
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
2717
2718
        // build the datagrid filter
2719
        $this->configureDatagridFilters($mapper);
2720
2721
        // ok, try to limit to add parent filter
2722
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
2723
            $mapper->add($this->getParentAssociationMapping(), null, [
2724
                'show_filter' => false,
2725
                'label' => false,
2726
                'field_type' => ModelHiddenType::class,
2727
                'field_options' => [
2728
                    'model_manager' => $this->getModelManager(),
2729
                ],
2730
                'operator_type' => HiddenType::class,
2731
            ], null, null, [
2732
                'admin_code' => $this->getParent()->getCode(),
2733
            ]);
2734
        }
2735
2736
        foreach ($this->getExtensions() as $extension) {
2737
            $extension->configureDatagridFilters($mapper);
2738
        }
2739
    }
2740
2741
    /**
2742
     * Build all the related urls to the current admin.
2743
     */
2744
    private function buildRoutes(): void
2745
    {
2746
        if ($this->loaded['routes']) {
2747
            return;
2748
        }
2749
2750
        $this->loaded['routes'] = true;
2751
2752
        $this->routes = new RouteCollection(
2753
            $this->getBaseCodeRoute(),
2754
            $this->getBaseRouteName(),
2755
            $this->getBaseRoutePattern(),
2756
            $this->getBaseControllerName()
2757
        );
2758
2759
        $this->routeBuilder->build($this, $this->routes);
2760
2761
        $this->configureRoutes($this->routes);
2762
2763
        foreach ($this->getExtensions() as $extension) {
2764
            $extension->configureRoutes($this, $this->routes);
2765
        }
2766
    }
2767
}
2768
2769
class_exists(ErrorElement::class);
2770