Completed
Pull Request — master (#6210)
by Jordi Sala
04:10
created

AbstractAdmin::attachAdminClass()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

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