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

AbstractAdmin::getFilterParameters()   B

Complexity

Conditions 11
Paths 81

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 50
rs 7.3166
c 0
b 0
f 0
cc 11
nc 81
nop 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

Loading history...
765
                throw new \RuntimeException(sprintf(
766
                    'Please define a default `baseRoutePattern` value for the admin class `%s`',
767
                    static::class
768
                ));
769
            }
770
771
            $this->cachedBaseRoutePattern = sprintf(
772
                '/%s%s/%s',
773
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
774
                $this->urlize($matches[3], '-'),
775
                $this->urlize($matches[5], '-')
776
            );
777
        }
778
779
        return $this->cachedBaseRoutePattern;
780
    }
781
782
    /**
783
     * Returns the baseRouteName used to generate the routing information.
784
     *
785
     * @throws \RuntimeException
786
     *
787
     * @return string the baseRouteName used to generate the routing information
788
     */
789
    public function getBaseRouteName(): string
790
    {
791
        if (null !== $this->cachedBaseRouteName) {
792
            return $this->cachedBaseRouteName;
793
        }
794
795
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
796
            $baseRouteName = $this->baseRouteName;
797
            if (!$this->baseRouteName) {
798
                preg_match(self::CLASS_REGEX, $this->class, $matches);
799
800
                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...
801
                    throw new \RuntimeException(sprintf(
802
                        'Cannot automatically determine base route name,'
803
                        .' please define a default `baseRouteName` value for the admin class `%s`',
804
                        static::class
805
                    ));
806
                }
807
                $baseRouteName = $this->urlize($matches[5]);
808
            }
809
810
            $this->cachedBaseRouteName = sprintf(
811
                '%s_%s',
812
                $this->getParent()->getBaseRouteName(),
813
                $baseRouteName
814
            );
815
        } elseif ($this->baseRouteName) {
816
            $this->cachedBaseRouteName = $this->baseRouteName;
817
        } else {
818
            preg_match(self::CLASS_REGEX, $this->class, $matches);
819
820
            if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

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