Completed
Pull Request — master (#6303)
by Vincent
03:45 queued 57s
created

AbstractAdmin::hasListFieldDescription()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

Loading history...
545
546
            // NEXT_MAJOR: Remove the following code in favor of the commented one.
547
            // If a key is provided we use it otherwise we use the generated label.
548
            // $fieldKey = \is_string($key) ? $key : $transLabel;
549
            // $fields[$fieldKey] = $field;
550
            if ($transLabel === $label) {
551
                $fields[$key] = $field;
552
            } else {
553
                $fields[$transLabel] = $field;
554
            }
555
        }
556
557
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
558
    }
559
560
    /**
561
     * define custom variable.
562
     */
563
    public function initialize(): void
564
    {
565
        if (!$this->classnameLabel) {
566
            $this->classnameLabel = substr($this->getClass(), strrpos($this->getClass(), '\\') + 1);
567
        }
568
569
        $this->configure();
570
    }
571
572
    public function update(object $object): object
573
    {
574
        $this->preUpdate($object);
575
        foreach ($this->extensions as $extension) {
576
            $extension->preUpdate($this, $object);
577
        }
578
579
        $result = $this->getModelManager()->update($object);
580
        // BC compatibility
581
        if (null !== $result) {
582
            $object = $result;
583
        }
584
585
        $this->postUpdate($object);
586
        foreach ($this->extensions as $extension) {
587
            $extension->postUpdate($this, $object);
588
        }
589
590
        return $object;
591
    }
592
593
    public function create(object $object): object
594
    {
595
        $this->prePersist($object);
596
        foreach ($this->extensions as $extension) {
597
            $extension->prePersist($this, $object);
598
        }
599
600
        $result = $this->getModelManager()->create($object);
601
        // BC compatibility
602
        if (null !== $result) {
603
            $object = $result;
604
        }
605
606
        $this->postPersist($object);
607
        foreach ($this->extensions as $extension) {
608
            $extension->postPersist($this, $object);
609
        }
610
611
        $this->createObjectSecurity($object);
612
613
        return $object;
614
    }
615
616
    public function delete(object $object): void
617
    {
618
        $this->preRemove($object);
619
        foreach ($this->extensions as $extension) {
620
            $extension->preRemove($this, $object);
621
        }
622
623
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
624
        $this->getModelManager()->delete($object);
625
626
        $this->postRemove($object);
627
        foreach ($this->extensions as $extension) {
628
            $extension->postRemove($this, $object);
629
        }
630
    }
631
632
    public function preValidate(object $object): void
633
    {
634
    }
635
636
    public function preUpdate(object $object): void
637
    {
638
    }
639
640
    public function postUpdate(object $object): void
641
    {
642
    }
643
644
    public function prePersist(object $object): void
645
    {
646
    }
647
648
    public function postPersist(object $object): void
649
    {
650
    }
651
652
    public function preRemove(object $object): void
653
    {
654
    }
655
656
    public function postRemove(object $object): void
657
    {
658
    }
659
660
    public function preBatchAction(string $actionName, ProxyQueryInterface $query, array &$idx, bool $allElements = false): void
661
    {
662
    }
663
664
    public function getFilterParameters(): array
665
    {
666
        $parameters = [];
667
668
        // build the values array
669
        if ($this->hasRequest()) {
670
            $filters = $this->request->query->get('filter', []);
671
            if (isset($filters['_page'])) {
672
                $filters['_page'] = (int) $filters['_page'];
673
            }
674
            if (isset($filters['_per_page'])) {
675
                $filters['_per_page'] = (int) $filters['_per_page'];
676
            }
677
678
            // if filter persistence is configured
679
            if (null !== $this->filterPersister) {
680
                // if reset filters is asked, remove from storage
681
                if ('reset' === $this->request->query->get('filters')) {
682
                    $this->filterPersister->reset($this->getCode());
683
                }
684
685
                // if no filters, fetch from storage
686
                // otherwise save to storage
687
                if (empty($filters)) {
688
                    $filters = $this->filterPersister->get($this->getCode());
689
                } else {
690
                    $this->filterPersister->set($this->getCode(), $filters);
691
                }
692
            }
693
694
            $parameters = array_merge(
695
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
696
                $this->getDefaultSortValues(),
697
                $this->getDefaultFilterValues(),
698
                $filters
699
            );
700
701
            if (!isset($parameters['_per_page']) || !$this->determinedPerPageValue($parameters['_per_page'])) {
702
                $parameters['_per_page'] = $this->getMaxPerPage();
703
            }
704
705
            // always force the parent value
706
            if ($this->isChild() && $this->getParentAssociationMapping()) {
707
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
708
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
709
            }
710
        }
711
712
        return $parameters;
713
    }
714
715
    /**
716
     * Returns the name of the parent related field, so the field can be use to set the default
717
     * value (ie the parent object) or to filter the object.
718
     *
719
     * @throws \InvalidArgumentException
720
     */
721
    public function getParentAssociationMapping(): ?string
722
    {
723
        if ($this->isChild()) {
724
            $parent = $this->getParent()->getCode();
725
726
            if (\array_key_exists($parent, $this->parentAssociationMapping)) {
727
                return $this->parentAssociationMapping[$parent];
728
            }
729
730
            throw new \InvalidArgumentException(sprintf(
731
                'There\'s no association between %s and %s.',
732
                $this->getCode(),
733
                $this->getParent()->getCode()
734
            ));
735
        }
736
737
        return null;
738
    }
739
740
    final public function addParentAssociationMapping(string $code, string $value): void
741
    {
742
        $this->parentAssociationMapping[$code] = $value;
743
    }
744
745
    /**
746
     * Returns the baseRoutePattern used to generate the routing information.
747
     *
748
     * @throws \RuntimeException
749
     *
750
     * @return string the baseRoutePattern used to generate the routing information
751
     */
752
    public function getBaseRoutePattern(): string
753
    {
754
        if (null !== $this->cachedBaseRoutePattern) {
755
            return $this->cachedBaseRoutePattern;
756
        }
757
758
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
759
            $baseRoutePattern = $this->baseRoutePattern;
760
            if (!$this->baseRoutePattern) {
761
                preg_match(self::CLASS_REGEX, $this->class, $matches);
762
763
                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...
764
                    throw new \RuntimeException(sprintf(
765
                        'Please define a default `baseRoutePattern` value for the admin class `%s`',
766
                        static::class
767
                    ));
768
                }
769
                $baseRoutePattern = $this->urlize($matches[5], '-');
770
            }
771
772
            $this->cachedBaseRoutePattern = sprintf(
773
                '%s/%s/%s',
774
                $this->getParent()->getBaseRoutePattern(),
775
                $this->getParent()->getRouterIdParameter(),
776
                $baseRoutePattern
777
            );
778
        } elseif ($this->baseRoutePattern) {
779
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
780
        } else {
781
            preg_match(self::CLASS_REGEX, $this->class, $matches);
782
783
            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...
784
                throw new \RuntimeException(sprintf(
785
                    'Please define a default `baseRoutePattern` value for the admin class `%s`',
786
                    static::class
787
                ));
788
            }
789
790
            $this->cachedBaseRoutePattern = sprintf(
791
                '/%s%s/%s',
792
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
793
                $this->urlize($matches[3], '-'),
794
                $this->urlize($matches[5], '-')
795
            );
796
        }
797
798
        return $this->cachedBaseRoutePattern;
799
    }
800
801
    /**
802
     * Returns the baseRouteName used to generate the routing information.
803
     *
804
     * @throws \RuntimeException
805
     *
806
     * @return string the baseRouteName used to generate the routing information
807
     */
808
    public function getBaseRouteName(): string
809
    {
810
        if (null !== $this->cachedBaseRouteName) {
811
            return $this->cachedBaseRouteName;
812
        }
813
814
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
815
            $baseRouteName = $this->baseRouteName;
816
            if (!$this->baseRouteName) {
817
                preg_match(self::CLASS_REGEX, $this->class, $matches);
818
819
                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...
820
                    throw new \RuntimeException(sprintf(
821
                        'Cannot automatically determine base route name,'
822
                        .' please define a default `baseRouteName` value for the admin class `%s`',
823
                        static::class
824
                    ));
825
                }
826
                $baseRouteName = $this->urlize($matches[5]);
827
            }
828
829
            $this->cachedBaseRouteName = sprintf(
830
                '%s_%s',
831
                $this->getParent()->getBaseRouteName(),
832
                $baseRouteName
833
            );
834
        } elseif ($this->baseRouteName) {
835
            $this->cachedBaseRouteName = $this->baseRouteName;
836
        } else {
837
            preg_match(self::CLASS_REGEX, $this->class, $matches);
838
839
            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...
840
                throw new \RuntimeException(sprintf(
841
                    'Cannot automatically determine base route name,'
842
                    .' please define a default `baseRouteName` value for the admin class `%s`',
843
                    static::class
844
                ));
845
            }
846
847
            $this->cachedBaseRouteName = sprintf(
848
                'admin_%s%s_%s',
849
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
850
                $this->urlize($matches[3]),
851
                $this->urlize($matches[5])
852
            );
853
        }
854
855
        return $this->cachedBaseRouteName;
856
    }
857
858
    public function getClass(): string
859
    {
860
        if ($this->hasActiveSubClass()) {
861
            if ($this->hasParentFieldDescription()) {
862
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
863
            }
864
865
            $subClass = $this->getRequest()->query->get('subclass');
866
867
            if (!$this->hasSubClass($subClass)) {
868
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
869
            }
870
871
            return $this->getSubClass($subClass);
872
        }
873
874
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
875
        if ($this->subject && \is_object($this->subject)) {
876
            return ClassUtils::getClass($this->subject);
877
        }
878
879
        return $this->class;
880
    }
881
882
    public function getSubClasses(): array
883
    {
884
        return $this->subClasses;
885
    }
886
887
    public function setSubClasses(array $subClasses): void
888
    {
889
        $this->subClasses = $subClasses;
890
    }
891
892
    public function hasSubClass(string $name): bool
893
    {
894
        return isset($this->subClasses[$name]);
895
    }
896
897
    public function hasActiveSubClass(): bool
898
    {
899
        if (\count($this->subClasses) > 0 && $this->request) {
900
            return null !== $this->getRequest()->query->get('subclass');
901
        }
902
903
        return false;
904
    }
905
906
    public function getActiveSubClass(): string
907
    {
908
        if (!$this->hasActiveSubClass()) {
909
            throw new \LogicException(sprintf(
910
                'Admin "%s" has no active subclass.',
911
                static::class
912
            ));
913
        }
914
915
        return $this->getSubClass($this->getActiveSubclassCode());
916
    }
917
918
    public function getActiveSubclassCode(): string
919
    {
920
        if (!$this->hasActiveSubClass()) {
921
            throw new \LogicException(sprintf(
922
                'Admin "%s" has no active subclass.',
923
                static::class
924
            ));
925
        }
926
927
        $subClass = $this->getRequest()->query->get('subclass');
928
929
        if (!$this->hasSubClass($subClass)) {
930
            throw new \LogicException(sprintf(
931
                'Admin "%s" has no active subclass.',
932
                static::class
933
            ));
934
        }
935
936
        return $subClass;
937
    }
938
939
    public function getBatchActions(): array
940
    {
941
        $actions = [];
942
943
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
944
            $actions['delete'] = [
945
                'label' => 'action_delete',
946
                'translation_domain' => 'SonataAdminBundle',
947
                'ask_confirmation' => true, // by default always true
948
            ];
949
        }
950
951
        $actions = $this->configureBatchActions($actions);
952
953
        foreach ($this->getExtensions() as $extension) {
954
            $actions = $extension->configureBatchActions($this, $actions);
955
        }
956
957
        foreach ($actions  as $name => &$action) {
958
            if (!\array_key_exists('label', $action)) {
959
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
960
            }
961
962
            if (!\array_key_exists('translation_domain', $action)) {
963
                $action['translation_domain'] = $this->getTranslationDomain();
964
            }
965
        }
966
967
        return $actions;
968
    }
969
970
    public function getRoutes(): RouteCollectionInterface
971
    {
972
        $this->buildRoutes();
973
974
        return $this->routes;
975
    }
976
977
    public function getRouterIdParameter(): string
978
    {
979
        return sprintf('{%s}', $this->getIdParameter());
980
    }
981
982
    public function getIdParameter(): string
983
    {
984
        $parameter = 'id';
985
986
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
987
            $parameter = sprintf('child%s', ucfirst($parameter));
988
        }
989
990
        return $parameter;
991
    }
992
993
    public function hasRoute(string $name): bool
994
    {
995
        if (!$this->routeGenerator) {
996
            throw new \RuntimeException('RouteGenerator cannot be null');
997
        }
998
999
        return $this->routeGenerator->hasAdminRoute($this, $name);
1000
    }
1001
1002
    public function isCurrentRoute(string $name, ?string $adminCode = null): bool
1003
    {
1004
        if (!$this->hasRequest()) {
1005
            return false;
1006
        }
1007
1008
        $request = $this->getRequest();
1009
        $route = $request->get('_route');
1010
1011
        if ($adminCode) {
1012
            $pool = $this->getConfigurationPool();
1013
1014
            if ($pool->hasAdminByAdminCode($adminCode)) {
1015
                $admin = $pool->getAdminByAdminCode($adminCode);
1016
            } else {
1017
                return false;
1018
            }
1019
        } else {
1020
            $admin = $this;
1021
        }
1022
1023
        return sprintf('%s_%s', $admin->getBaseRouteName(), $name) === $route;
1024
    }
1025
1026
    public function generateObjectUrl(string $name, object $object, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1027
    {
1028
        $parameters['id'] = $this->getUrlSafeIdentifier($object);
1029
1030
        return $this->generateUrl($name, $parameters, $referenceType);
1031
    }
1032
1033
    public function generateUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
1034
    {
1035
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $referenceType);
1036
    }
1037
1038
    public function generateMenuUrl(string $name, array $parameters = [], int $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH): array
1039
    {
1040
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $referenceType);
1041
    }
1042
1043
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry): void
1044
    {
1045
        $this->templateRegistry = $templateRegistry;
1046
    }
1047
1048
    /**
1049
     * @param array<string, string> $templates
1050
     */
1051
    public function setTemplates(array $templates): void
1052
    {
1053
        $this->getTemplateRegistry()->setTemplates($templates);
1054
    }
1055
1056
    /**
1057
     * {@inheritdoc}
1058
     */
1059
    public function setTemplate(string $name, string $template): void
1060
    {
1061
        $this->getTemplateRegistry()->setTemplate($name, $template);
1062
    }
1063
1064
    public function getNewInstance(): object
1065
    {
1066
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1067
1068
        $this->appendParentObject($object);
1069
1070
        foreach ($this->getExtensions() as $extension) {
1071
            $extension->alterNewInstance($this, $object);
1072
        }
1073
1074
        return $object;
1075
    }
1076
1077
    public function getFormBuilder(): FormBuilderInterface
1078
    {
1079
        $this->formOptions['data_class'] = $this->getClass();
1080
1081
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1082
            $this->getUniqid(),
1083
            $this->formOptions
1084
        );
1085
1086
        $this->defineFormBuilder($formBuilder);
1087
1088
        return $formBuilder;
1089
    }
1090
1091
    /**
1092
     * This method is being called by the main admin class and the child class,
1093
     * the getFormBuilder is only call by the main admin class.
1094
     */
1095
    public function defineFormBuilder(FormBuilderInterface $formBuilder): void
1096
    {
1097
        if (!$this->hasSubject()) {
1098
            throw new \LogicException(sprintf(
1099
                'Admin "%s" has no subject.',
1100
                static::class
1101
            ));
1102
        }
1103
1104
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1105
1106
        $this->configureFormFields($mapper);
1107
1108
        foreach ($this->getExtensions() as $extension) {
1109
            $extension->configureFormFields($mapper);
1110
        }
1111
    }
1112
1113
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription): void
1114
    {
1115
        $pool = $this->getConfigurationPool();
1116
1117
        $adminCode = $fieldDescription->getOption('admin_code');
1118
1119
        if (null !== $adminCode) {
1120
            if (!$pool->hasAdminByAdminCode($adminCode)) {
1121
                return;
1122
            }
1123
1124
            $admin = $pool->getAdminByAdminCode($adminCode);
1125
        } else {
1126
            $targetModel = $fieldDescription->getTargetModel();
1127
1128
            if (!$pool->hasAdminByClass($targetModel)) {
1129
                return;
1130
            }
1131
1132
            $admin = $pool->getAdminByClass($targetModel);
1133
        }
1134
1135
        if ($this->hasRequest()) {
1136
            $admin->setRequest($this->getRequest());
1137
        }
1138
1139
        $fieldDescription->setAssociationAdmin($admin);
1140
    }
1141
1142
    public function getObject($id): ?object
1143
    {
1144
        $object = $this->getModelManager()->find($this->getClass(), $id);
1145
        foreach ($this->getExtensions() as $extension) {
1146
            $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...
1147
        }
1148
1149
        return $object;
1150
    }
1151
1152
    public function getForm(): ?FormInterface
1153
    {
1154
        $this->buildForm();
1155
1156
        return $this->form;
1157
    }
1158
1159
    public function getList(): ?FieldDescriptionCollection
1160
    {
1161
        $this->buildList();
1162
1163
        return $this->list;
1164
    }
1165
1166
    final public function createQuery(): ProxyQueryInterface
1167
    {
1168
        $query = $this->getModelManager()->createQuery($this->getClass());
1169
1170
        $query = $this->configureQuery($query);
1171
        foreach ($this->extensions as $extension) {
1172
            $extension->configureQuery($this, $query);
1173
        }
1174
1175
        return $query;
1176
    }
1177
1178
    public function getDatagrid(): DatagridInterface
1179
    {
1180
        $this->buildDatagrid();
1181
1182
        return $this->datagrid;
1183
    }
1184
1185
    public function buildTabMenu(string $action, ?AdminInterface $childAdmin = null): ItemInterface
1186
    {
1187
        if ($this->loaded['tab_menu']) {
1188
            return $this->menu;
1189
        }
1190
1191
        $this->loaded['tab_menu'] = true;
1192
1193
        $menu = $this->menuFactory->createItem('root');
1194
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1195
        $menu->setExtra('translation_domain', $this->translationDomain);
1196
1197
        // Prevents BC break with KnpMenuBundle v1.x
1198
        if (method_exists($menu, 'setCurrentUri')) {
1199
            $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...
1200
        }
1201
1202
        $this->configureTabMenu($menu, $action, $childAdmin);
1203
1204
        foreach ($this->getExtensions() as $extension) {
1205
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1206
        }
1207
1208
        $this->menu = $menu;
1209
1210
        return $this->menu;
1211
    }
1212
1213
    public function getSideMenu(string $action, ?AdminInterface $childAdmin = null): ItemInterface
1214
    {
1215
        if ($this->isChild()) {
1216
            return $this->getParent()->getSideMenu($action, $this);
1217
        }
1218
1219
        $this->buildTabMenu($action, $childAdmin);
1220
1221
        return $this->menu;
1222
    }
1223
1224
    public function getRootCode(): string
1225
    {
1226
        return $this->getRoot()->getCode();
1227
    }
1228
1229
    public function getRoot(): AdminInterface
1230
    {
1231
        if (!$this->hasParentFieldDescription()) {
1232
            return $this;
1233
        }
1234
1235
        return $this->getParentFieldDescription()->getAdmin()->getRoot();
1236
    }
1237
1238
    public function setBaseControllerName(string $baseControllerName): void
1239
    {
1240
        $this->baseControllerName = $baseControllerName;
1241
    }
1242
1243
    public function getBaseControllerName(): string
1244
    {
1245
        return $this->baseControllerName;
1246
    }
1247
1248
    public function setLabel(?string $label): void
1249
    {
1250
        $this->label = $label;
1251
    }
1252
1253
    public function getLabel(): ?string
1254
    {
1255
        return $this->label;
1256
    }
1257
1258
    public function setFilterPersister(?FilterPersisterInterface $filterPersister = null): void
1259
    {
1260
        $this->filterPersister = $filterPersister;
1261
    }
1262
1263
    public function getMaxPerPage(): int
1264
    {
1265
        $sortValues = $this->getModelManager()->getDefaultSortValues($this->class);
1266
1267
        return $sortValues['_per_page'] ?? 25;
1268
    }
1269
1270
    public function setMaxPageLinks(int $maxPageLinks): void
1271
    {
1272
        $this->maxPageLinks = $maxPageLinks;
1273
    }
1274
1275
    public function getMaxPageLinks(): int
1276
    {
1277
        return $this->maxPageLinks;
1278
    }
1279
1280
    public function getFormGroups(): array
1281
    {
1282
        return $this->formGroups;
1283
    }
1284
1285
    public function setFormGroups(array $formGroups): void
1286
    {
1287
        $this->formGroups = $formGroups;
1288
    }
1289
1290
    public function removeFieldFromFormGroup(string $key): void
1291
    {
1292
        foreach ($this->formGroups as $name => $formGroup) {
1293
            unset($this->formGroups[$name]['fields'][$key]);
1294
1295
            if (empty($this->formGroups[$name]['fields'])) {
1296
                unset($this->formGroups[$name]);
1297
            }
1298
        }
1299
    }
1300
1301
    public function reorderFormGroup(string $group, array $keys): void
1302
    {
1303
        $formGroups = $this->getFormGroups();
1304
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1305
        $this->setFormGroups($formGroups);
1306
    }
1307
1308
    public function getFormTabs(): array
1309
    {
1310
        return $this->formTabs;
1311
    }
1312
1313
    public function setFormTabs(array $formTabs): void
1314
    {
1315
        $this->formTabs = $formTabs;
1316
    }
1317
1318
    public function getShowTabs(): array
1319
    {
1320
        return $this->showTabs;
1321
    }
1322
1323
    public function setShowTabs(array $showTabs): void
1324
    {
1325
        $this->showTabs = $showTabs;
1326
    }
1327
1328
    public function getShowGroups(): array
1329
    {
1330
        return $this->showGroups;
1331
    }
1332
1333
    public function setShowGroups(array $showGroups): void
1334
    {
1335
        $this->showGroups = $showGroups;
1336
    }
1337
1338
    public function reorderShowGroup(string $group, array $keys): void
1339
    {
1340
        $showGroups = $this->getShowGroups();
1341
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1342
        $this->setShowGroups($showGroups);
1343
    }
1344
1345
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription): void
1346
    {
1347
        $this->parentFieldDescription = $parentFieldDescription;
1348
    }
1349
1350
    public function getParentFieldDescription(): FieldDescriptionInterface
1351
    {
1352
        if (!$this->hasParentFieldDescription()) {
1353
            throw new \LogicException(sprintf(
1354
                'Admin "%s" has no parent field description.',
1355
                static::class
1356
            ));
1357
        }
1358
1359
        return $this->parentFieldDescription;
1360
    }
1361
1362
    public function hasParentFieldDescription(): bool
1363
    {
1364
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1365
    }
1366
1367
    public function setSubject(?object $subject): void
1368
    {
1369
        if (\is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1370
            throw new \LogicException(sprintf(
1371
                'Admin "%s" does not allow this subject: %s, use the one register with this admin class %s',
1372
                static::class,
1373
                \get_class($subject),
1374
                $this->getClass()
1375
            ));
1376
        }
1377
1378
        $this->subject = $subject;
1379
    }
1380
1381
    public function getSubject(): object
1382
    {
1383
        if (!$this->hasSubject()) {
1384
            throw new \LogicException(sprintf(
1385
                'Admin "%s" has no subject.',
1386
                static::class
1387
            ));
1388
        }
1389
1390
        return $this->subject;
1391
    }
1392
1393
    public function hasSubject(): bool
1394
    {
1395
        if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
1396
            $id = $this->request->get($this->getIdParameter());
1397
1398
            if (null !== $id) {
1399
                $this->subject = $this->getObject($id);
1400
            }
1401
        }
1402
1403
        return null !== $this->subject;
1404
    }
1405
1406
    public function getFormFieldDescriptions(): array
1407
    {
1408
        $this->buildForm();
1409
1410
        return $this->formFieldDescriptions;
1411
    }
1412
1413
    public function getFormFieldDescription(string $name): FieldDescriptionInterface
1414
    {
1415
        $this->buildForm();
1416
1417
        if (!$this->hasFormFieldDescription($name)) {
1418
            throw new \LogicException(sprintf(
1419
                'Admin "%s" has no form field description for the field %s.',
1420
                static::class,
1421
                $name
1422
            ));
1423
        }
1424
1425
        return $this->formFieldDescriptions[$name];
1426
    }
1427
1428
    /**
1429
     * Returns true if the admin has a FieldDescription with the given $name.
1430
     */
1431
    public function hasFormFieldDescription(string $name): bool
1432
    {
1433
        $this->buildForm();
1434
1435
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1436
    }
1437
1438
    public function addFormFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1439
    {
1440
        $this->formFieldDescriptions[$name] = $fieldDescription;
1441
    }
1442
1443
    /**
1444
     * remove a FieldDescription.
1445
     */
1446
    public function removeFormFieldDescription(string $name): void
1447
    {
1448
        unset($this->formFieldDescriptions[$name]);
1449
    }
1450
1451
    /**
1452
     * build and return the collection of form FieldDescription.
1453
     *
1454
     * @return FieldDescriptionInterface[] collection of form FieldDescription
1455
     */
1456
    public function getShowFieldDescriptions(): array
1457
    {
1458
        $this->buildShow();
1459
1460
        return $this->showFieldDescriptions;
1461
    }
1462
1463
    /**
1464
     * Returns the form FieldDescription with the given $name.
1465
     */
1466
    public function getShowFieldDescription(string $name): FieldDescriptionInterface
1467
    {
1468
        $this->buildShow();
1469
1470
        if (!$this->hasShowFieldDescription($name)) {
1471
            throw new \LogicException(sprintf(
1472
                'Admin "%s" has no show field description for the field %s.',
1473
                static::class,
1474
                $name
1475
            ));
1476
        }
1477
1478
        return $this->showFieldDescriptions[$name];
1479
    }
1480
1481
    public function hasShowFieldDescription(string $name): bool
1482
    {
1483
        $this->buildShow();
1484
1485
        return \array_key_exists($name, $this->showFieldDescriptions);
1486
    }
1487
1488
    public function addShowFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1489
    {
1490
        $this->showFieldDescriptions[$name] = $fieldDescription;
1491
    }
1492
1493
    public function removeShowFieldDescription(string $name): void
1494
    {
1495
        unset($this->showFieldDescriptions[$name]);
1496
    }
1497
1498
    public function getListFieldDescriptions(): array
1499
    {
1500
        $this->buildList();
1501
1502
        return $this->listFieldDescriptions;
1503
    }
1504
1505
    public function getListFieldDescription(string $name): FieldDescriptionInterface
1506
    {
1507
        $this->buildList();
1508
1509
        if (!$this->hasListFieldDescription($name)) {
1510
            throw new \LogicException(sprintf(
1511
                'Admin "%s" has no list field description for %s.',
1512
                static::class,
1513
                $name
1514
            ));
1515
        }
1516
1517
        return $this->listFieldDescriptions[$name];
1518
    }
1519
1520
    public function hasListFieldDescription(string $name): bool
1521
    {
1522
        $this->buildList();
1523
1524
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1525
    }
1526
1527
    public function addListFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1528
    {
1529
        $this->listFieldDescriptions[$name] = $fieldDescription;
1530
    }
1531
1532
    public function removeListFieldDescription(string $name): void
1533
    {
1534
        unset($this->listFieldDescriptions[$name]);
1535
    }
1536
1537
    public function getFilterFieldDescription(string $name): FieldDescriptionInterface
1538
    {
1539
        $this->buildDatagrid();
1540
1541
        if (!$this->hasFilterFieldDescription($name)) {
1542
            throw new \LogicException(sprintf(
1543
                'Admin "%s" has no filter field description for the field %s.',
1544
                static::class,
1545
                $name
1546
            ));
1547
        }
1548
1549
        return $this->filterFieldDescriptions[$name];
1550
    }
1551
1552
    public function hasFilterFieldDescription(string $name): bool
1553
    {
1554
        $this->buildDatagrid();
1555
1556
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1557
    }
1558
1559
    public function addFilterFieldDescription(string $name, FieldDescriptionInterface $fieldDescription): void
1560
    {
1561
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1562
    }
1563
1564
    public function removeFilterFieldDescription(string $name): void
1565
    {
1566
        unset($this->filterFieldDescriptions[$name]);
1567
    }
1568
1569
    public function getFilterFieldDescriptions(): array
1570
    {
1571
        $this->buildDatagrid();
1572
1573
        return $this->filterFieldDescriptions;
1574
    }
1575
1576
    public function addChild(AdminInterface $child, string $field): void
1577
    {
1578
        $parentAdmin = $this;
1579
        while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
1580
            $parentAdmin = $parentAdmin->getParent();
1581
        }
1582
1583
        if ($parentAdmin->getCode() === $child->getCode()) {
1584
            throw new \RuntimeException(sprintf(
1585
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1586
                $child->getCode(),
1587
                $this->getCode()
1588
            ));
1589
        }
1590
1591
        $this->children[$child->getCode()] = $child;
1592
1593
        $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...
1594
        $child->addParentAssociationMapping($this->getCode(), $field);
1595
    }
1596
1597
    public function hasChild(string $code): bool
1598
    {
1599
        return isset($this->children[$code]);
1600
    }
1601
1602
    public function getChildren(): array
1603
    {
1604
        return $this->children;
1605
    }
1606
1607
    public function getChild(string $code): AdminInterface
1608
    {
1609
        if (!$this->hasChild($code)) {
1610
            throw new \LogicException(sprintf(
1611
                'Admin "%s" has no child for the code %s.',
1612
                static::class,
1613
                $code
1614
            ));
1615
        }
1616
1617
        return $this->children[$code];
1618
    }
1619
1620
    public function setParent(AdminInterface $parent): void
1621
    {
1622
        $this->parent = $parent;
1623
    }
1624
1625
    public function getParent(): AdminInterface
1626
    {
1627
        if (!$this->isChild()) {
1628
            throw new \LogicException(sprintf(
1629
                'Admin "%s" has no parent.',
1630
                static::class
1631
            ));
1632
        }
1633
1634
        return $this->parent;
1635
    }
1636
1637
    final public function getRootAncestor(): AdminInterface
1638
    {
1639
        $parent = $this;
1640
1641
        while ($parent->isChild()) {
1642
            $parent = $parent->getParent();
1643
        }
1644
1645
        return $parent;
1646
    }
1647
1648
    final public function getChildDepth(): int
1649
    {
1650
        $parent = $this;
1651
        $depth = 0;
1652
1653
        while ($parent->isChild()) {
1654
            $parent = $parent->getParent();
1655
            ++$depth;
1656
        }
1657
1658
        return $depth;
1659
    }
1660
1661
    final public function getCurrentLeafChildAdmin(): ?AdminInterface
1662
    {
1663
        $child = $this->getCurrentChildAdmin();
1664
1665
        if (null === $child) {
1666
            return null;
1667
        }
1668
1669
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1670
            $child = $c;
1671
        }
1672
1673
        return $child;
1674
    }
1675
1676
    public function isChild(): bool
1677
    {
1678
        return $this->parent instanceof AdminInterface;
1679
    }
1680
1681
    /**
1682
     * Returns true if the admin has children, false otherwise.
1683
     */
1684
    public function hasChildren(): bool
1685
    {
1686
        return \count($this->children) > 0;
1687
    }
1688
1689
    public function setUniqid(string $uniqid): void
1690
    {
1691
        $this->uniqid = $uniqid;
1692
    }
1693
1694
    public function getUniqid(): string
1695
    {
1696
        if (!$this->uniqid) {
1697
            $this->uniqid = sprintf('s%s', uniqid());
1698
        }
1699
1700
        return $this->uniqid;
1701
    }
1702
1703
    /**
1704
     * {@inheritdoc}
1705
     */
1706
    public function getClassnameLabel(): string
1707
    {
1708
        return $this->classnameLabel;
1709
    }
1710
1711
    public function getPersistentParameters(): array
1712
    {
1713
        $parameters = [];
1714
1715
        foreach ($this->getExtensions() as $extension) {
1716
            $params = $extension->getPersistentParameters($this);
1717
1718
            $parameters = array_merge($parameters, $params);
1719
        }
1720
1721
        return $parameters;
1722
    }
1723
1724
    /**
1725
     * {@inheritdoc}
1726
     */
1727
    public function getPersistentParameter(string $name)
1728
    {
1729
        $parameters = $this->getPersistentParameters();
1730
1731
        return $parameters[$name] ?? null;
1732
    }
1733
1734
    public function setCurrentChild(bool $currentChild): void
1735
    {
1736
        $this->currentChild = $currentChild;
1737
    }
1738
1739
    public function isCurrentChild(): bool
1740
    {
1741
        return $this->currentChild;
1742
    }
1743
1744
    /**
1745
     * Returns the current child admin instance.
1746
     *
1747
     * @return AdminInterface|null the current child admin instance
1748
     */
1749
    public function getCurrentChildAdmin(): ?AdminInterface
1750
    {
1751
        foreach ($this->children as $children) {
1752
            if ($children->isCurrentChild()) {
1753
                return $children;
1754
            }
1755
        }
1756
1757
        return null;
1758
    }
1759
1760
    public function setTranslationDomain(string $translationDomain): void
1761
    {
1762
        $this->translationDomain = $translationDomain;
1763
    }
1764
1765
    public function getTranslationDomain(): string
1766
    {
1767
        return $this->translationDomain;
1768
    }
1769
1770
    /**
1771
     * {@inheritdoc}
1772
     *
1773
     * NEXT_MAJOR: remove this method
1774
     *
1775
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
1776
     */
1777
    public function setTranslator(TranslatorInterface $translator): void
1778
    {
1779
        $args = \func_get_args();
1780
        if (isset($args[1]) && $args[1]) {
1781
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1782
                'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
1783
                __METHOD__
1784
            ), E_USER_DEPRECATED);
1785
        }
1786
1787
        $this->translator = $translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since sonata-project/admin-bundle 3.9, to be removed with 4.0

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

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

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