Completed
Push — 3.x ( 3e834f...38b337 )
by Grégoire
03:36
created

src/Admin/AbstractAdmin.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Model\ModelManagerInterface;
33
use Sonata\AdminBundle\Object\Metadata;
34
use Sonata\AdminBundle\Route\RouteCollection;
35
use Sonata\AdminBundle\Route\RouteGeneratorInterface;
36
use Sonata\AdminBundle\Security\Handler\AclSecurityHandlerInterface;
37
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
38
use Sonata\AdminBundle\Show\ShowMapper;
39
use Sonata\AdminBundle\Templating\MutableTemplateRegistryInterface;
40
use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface;
41
use Sonata\Form\Validator\Constraints\InlineConstraint;
42
use Sonata\Form\Validator\ErrorElement;
43
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
44
use Symfony\Component\Form\Form;
45
use Symfony\Component\Form\FormBuilderInterface;
46
use Symfony\Component\Form\FormEvent;
47
use Symfony\Component\Form\FormEvents;
48
use Symfony\Component\HttpFoundation\Request;
49
use Symfony\Component\PropertyAccess\PropertyPath;
50
use Symfony\Component\Routing\Generator\UrlGeneratorInterface as RoutingUrlGeneratorInterface;
51
use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
52
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
53
use Symfony\Component\Translation\TranslatorInterface;
54
use Symfony\Component\Validator\Validator\ValidatorInterface;
55
56
/**
57
 * @author Thomas Rabaix <[email protected]>
58
 */
59
abstract class AbstractAdmin implements AdminInterface, DomainObjectInterface, AdminTreeInterface
60
{
61
    public const CONTEXT_MENU = 'menu';
62
    public const CONTEXT_DASHBOARD = 'dashboard';
63
64
    public const CLASS_REGEX =
65
        '@
66
        (?:([A-Za-z0-9]*)\\\)?        # vendor name / app name
67
        (Bundle\\\)?                  # optional bundle directory
68
        ([A-Za-z0-9]+?)(?:Bundle)?\\\ # bundle name, with optional suffix
69
        (
70
            Entity|Document|Model|PHPCR|CouchDocument|Phpcr|
71
            Doctrine\\\Orm|Doctrine\\\Phpcr|Doctrine\\\MongoDB|Doctrine\\\CouchDB
72
        )\\\(.*)@x';
73
74
    public const MOSAIC_ICON_CLASS = 'fa fa-th-large fa-fw';
75
76
    /**
77
     * The list FieldDescription constructed from the configureListField method.
78
     *
79
     * @var FieldDescriptionInterface[]
80
     */
81
    protected $listFieldDescriptions = [];
82
83
    /**
84
     * The show FieldDescription constructed from the configureShowFields method.
85
     *
86
     * @var FieldDescriptionInterface[]
87
     */
88
    protected $showFieldDescriptions = [];
89
90
    /**
91
     * The list FieldDescription constructed from the configureFormField method.
92
     *
93
     * @var FieldDescriptionInterface[]
94
     */
95
    protected $formFieldDescriptions = [];
96
97
    /**
98
     * The filter FieldDescription constructed from the configureFilterField method.
99
     *
100
     * @var FieldDescriptionInterface[]
101
     */
102
    protected $filterFieldDescriptions = [];
103
104
    /**
105
     * NEXT_MAJOR: Remove this property.
106
     *
107
     * The number of result to display in the list.
108
     *
109
     * @deprecated since sonata-project/admin-bundle 3.67.
110
     *
111
     * @var int
112
     */
113
    protected $maxPerPage = 32;
114
115
    /**
116
     * The maximum number of page numbers to display in the list.
117
     *
118
     * @var int
119
     */
120
    protected $maxPageLinks = 25;
121
122
    /**
123
     * The base route name used to generate the routing information.
124
     *
125
     * @var string
126
     */
127
    protected $baseRouteName;
128
129
    /**
130
     * The base route pattern used to generate the routing information.
131
     *
132
     * @var string
133
     */
134
    protected $baseRoutePattern;
135
136
    /**
137
     * The base name controller used to generate the routing information.
138
     *
139
     * @var string
140
     */
141
    protected $baseControllerName;
142
143
    /**
144
     * The label class name  (used in the title/breadcrumb ...).
145
     *
146
     * @var string
147
     */
148
    protected $classnameLabel;
149
150
    /**
151
     * The translation domain to be used to translate messages.
152
     *
153
     * @var string
154
     */
155
    protected $translationDomain = 'messages';
156
157
    /**
158
     * Options to set to the form (ie, validation_groups).
159
     *
160
     * @var array
161
     */
162
    protected $formOptions = [];
163
164
    /**
165
     * NEXT_MAJOR: Remove this property.
166
     *
167
     * Default values to the datagrid.
168
     *
169
     * @deprecated since sonata-project/admin-bundle 3.67, use configureDefaultSortValues() instead.
170
     *
171
     * @var array
172
     */
173
    protected $datagridValues = [
174
        '_page' => 1,
175
        '_per_page' => 32,
176
    ];
177
178
    /**
179
     * NEXT_MAJOR: Remove this property.
180
     *
181
     * Predefined per page options.
182
     *
183
     * @deprecated since sonata-project/admin-bundle 3.67.
184
     *
185
     * @var array
186
     */
187
    protected $perPageOptions = [16, 32, 64, 128, 256];
188
189
    /**
190
     * Pager type.
191
     *
192
     * @var string
193
     */
194
    protected $pagerType = Pager::TYPE_DEFAULT;
195
196
    /**
197
     * The code related to the admin.
198
     *
199
     * @var string
200
     */
201
    protected $code;
202
203
    /**
204
     * The label.
205
     *
206
     * @var string
207
     */
208
    protected $label;
209
210
    /**
211
     * Whether or not to persist the filters in the session.
212
     *
213
     * NEXT_MAJOR: remove this property
214
     *
215
     * @var bool
216
     *
217
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
218
     */
219
    protected $persistFilters = false;
220
221
    /**
222
     * Array of routes related to this admin.
223
     *
224
     * @var RouteCollection
225
     */
226
    protected $routes;
227
228
    /**
229
     * The subject only set in edit/update/create mode.
230
     *
231
     * @var object|null
232
     */
233
    protected $subject;
234
235
    /**
236
     * Define a Collection of child admin, ie /admin/order/{id}/order-element/{childId}.
237
     *
238
     * @var array
239
     */
240
    protected $children = [];
241
242
    /**
243
     * Reference the parent admin.
244
     *
245
     * @var AdminInterface|null
246
     */
247
    protected $parent;
248
249
    /**
250
     * The base code route refer to the prefix used to generate the route name.
251
     *
252
     * NEXT_MAJOR: remove this attribute.
253
     *
254
     * @deprecated This attribute is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0
255
     *
256
     * @var string
257
     */
258
    protected $baseCodeRoute = '';
259
260
    /**
261
     * NEXT_MAJOR: should be default array and private.
262
     *
263
     * @var string|array
264
     */
265
    protected $parentAssociationMapping;
266
267
    /**
268
     * Reference the parent FieldDescription related to this admin
269
     * only set for FieldDescription which is associated to an Sub Admin instance.
270
     *
271
     * @var FieldDescriptionInterface
272
     */
273
    protected $parentFieldDescription;
274
275
    /**
276
     * If true then the current admin is part of the nested admin set (from the url).
277
     *
278
     * @var bool
279
     */
280
    protected $currentChild = false;
281
282
    /**
283
     * The uniqid is used to avoid clashing with 2 admin related to the code
284
     * ie: a Block linked to a Block.
285
     *
286
     * @var string
287
     */
288
    protected $uniqid;
289
290
    /**
291
     * The Entity or Document manager.
292
     *
293
     * @var ModelManagerInterface
294
     */
295
    protected $modelManager;
296
297
    /**
298
     * The current request object.
299
     *
300
     * @var Request|null
301
     */
302
    protected $request;
303
304
    /**
305
     * The translator component.
306
     *
307
     * NEXT_MAJOR: remove this property
308
     *
309
     * @var \Symfony\Component\Translation\TranslatorInterface
310
     *
311
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
312
     */
313
    protected $translator;
314
315
    /**
316
     * The related form contractor.
317
     *
318
     * @var FormContractorInterface
319
     */
320
    protected $formContractor;
321
322
    /**
323
     * The related list builder.
324
     *
325
     * @var ListBuilderInterface
326
     */
327
    protected $listBuilder;
328
329
    /**
330
     * The related view builder.
331
     *
332
     * @var ShowBuilderInterface
333
     */
334
    protected $showBuilder;
335
336
    /**
337
     * The related datagrid builder.
338
     *
339
     * @var DatagridBuilderInterface
340
     */
341
    protected $datagridBuilder;
342
343
    /**
344
     * @var RouteBuilderInterface
345
     */
346
    protected $routeBuilder;
347
348
    /**
349
     * The datagrid instance.
350
     *
351
     * @var DatagridInterface|null
352
     */
353
    protected $datagrid;
354
355
    /**
356
     * The router instance.
357
     *
358
     * @var RouteGeneratorInterface|null
359
     */
360
    protected $routeGenerator;
361
362
    /**
363
     * The generated breadcrumbs.
364
     *
365
     * NEXT_MAJOR : remove this property
366
     *
367
     * @var array
368
     */
369
    protected $breadcrumbs = [];
370
371
    /**
372
     * @var SecurityHandlerInterface
373
     */
374
    protected $securityHandler;
375
376
    /**
377
     * @var ValidatorInterface
378
     */
379
    protected $validator;
380
381
    /**
382
     * The configuration pool.
383
     *
384
     * @var Pool
385
     */
386
    protected $configurationPool;
387
388
    /**
389
     * @var ItemInterface
390
     */
391
    protected $menu;
392
393
    /**
394
     * @var FactoryInterface
395
     */
396
    protected $menuFactory;
397
398
    /**
399
     * @var array<string, bool>
400
     */
401
    protected $loaded = [
402
        'view_fields' => false, // NEXT_MAJOR: Remove this unused value.
403
        'view_groups' => false, // NEXT_MAJOR: Remove this unused value.
404
        'routes' => false,
405
        'tab_menu' => false,
406
        'show' => false,
407
        'list' => false,
408
        'form' => false,
409
        'datagrid' => false,
410
    ];
411
412
    /**
413
     * @var string[]
414
     */
415
    protected $formTheme = [];
416
417
    /**
418
     * @var string[]
419
     */
420
    protected $filterTheme = [];
421
422
    /**
423
     * @var array<string, string>
424
     *
425
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
426
     */
427
    protected $templates = [];
428
429
    /**
430
     * @var AdminExtensionInterface[]
431
     */
432
    protected $extensions = [];
433
434
    /**
435
     * @var LabelTranslatorStrategyInterface
436
     */
437
    protected $labelTranslatorStrategy;
438
439
    /**
440
     * Setting to true will enable preview mode for
441
     * the entity and show a preview button in the
442
     * edit/create forms.
443
     *
444
     * @var bool
445
     */
446
    protected $supportsPreviewMode = false;
447
448
    /**
449
     * Roles and permissions per role.
450
     *
451
     * @var array 'role' => ['permission', 'permission']
452
     */
453
    protected $securityInformation = [];
454
455
    protected $cacheIsGranted = [];
456
457
    /**
458
     * Action list for the search result.
459
     *
460
     * @var string[]
461
     */
462
    protected $searchResultActions = ['edit', 'show'];
463
464
    protected $listModes = [
465
        'list' => [
466
            'class' => 'fa fa-list fa-fw',
467
        ],
468
        'mosaic' => [
469
            'class' => self::MOSAIC_ICON_CLASS,
470
        ],
471
    ];
472
473
    /**
474
     * The Access mapping.
475
     *
476
     * @var array [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
477
     */
478
    protected $accessMapping = [];
479
480
    /**
481
     * @var MutableTemplateRegistryInterface
482
     */
483
    private $templateRegistry;
484
485
    /**
486
     * The class name managed by the admin class.
487
     *
488
     * @var string
489
     */
490
    private $class;
491
492
    /**
493
     * The subclasses supported by the admin class.
494
     *
495
     * @var array<string, string>
496
     */
497
    private $subClasses = [];
498
499
    /**
500
     * The list collection.
501
     *
502
     * @var FieldDescriptionCollection|null
503
     */
504
    private $list;
505
506
    /**
507
     * @var FieldDescriptionCollection|null
508
     */
509
    private $show;
510
511
    /**
512
     * @var Form|null
513
     */
514
    private $form;
515
516
    /**
517
     * The cached base route name.
518
     *
519
     * @var string
520
     */
521
    private $cachedBaseRouteName;
522
523
    /**
524
     * The cached base route pattern.
525
     *
526
     * @var string
527
     */
528
    private $cachedBaseRoutePattern;
529
530
    /**
531
     * The form group disposition.
532
     *
533
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
534
     * hold boolean values.
535
     *
536
     * @var array|bool
537
     */
538
    private $formGroups = false;
539
540
    /**
541
     * The form tabs disposition.
542
     *
543
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
544
     * hold boolean values.
545
     *
546
     * @var array|bool
547
     */
548
    private $formTabs = false;
549
550
    /**
551
     * The view group disposition.
552
     *
553
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
554
     * hold boolean values.
555
     *
556
     * @var array|bool
557
     */
558
    private $showGroups = false;
559
560
    /**
561
     * The view tab disposition.
562
     *
563
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
564
     * hold boolean values.
565
     *
566
     * @var array|bool
567
     */
568
    private $showTabs = false;
569
570
    /**
571
     * The manager type to use for the admin.
572
     *
573
     * @var string
574
     */
575
    private $managerType;
576
577
    /**
578
     * The breadcrumbsBuilder component.
579
     *
580
     * @var BreadcrumbsBuilderInterface
581
     */
582
    private $breadcrumbsBuilder;
583
584
    /**
585
     * Component responsible for persisting filters.
586
     *
587
     * @var FilterPersisterInterface|null
588
     */
589
    private $filterPersister;
590
591
    /**
592
     * @param string      $code
593
     * @param string      $class
594
     * @param string|null $baseControllerName
595
     */
596
    public function __construct($code, $class, $baseControllerName = null)
597
    {
598
        if (!\is_string($code)) {
599
            @trigger_error(sprintf(
600
                'Passing other type than string as argument 1 for method %s() is deprecated since sonata-project/admin-bundle 3.65. It will accept only string in version 4.0.',
601
                __METHOD__
602
            ), E_USER_DEPRECATED);
603
        }
604
        $this->code = $code;
605
        if (!\is_string($class)) {
606
            @trigger_error(sprintf(
607
                'Passing other type than string as argument 2 for method %s() is deprecated since sonata-project/admin-bundle 3.65. It will accept only string in version 4.0.',
608
                __METHOD__
609
            ), E_USER_DEPRECATED);
610
        }
611
        $this->class = $class;
612
        if (null !== $baseControllerName && !\is_string($baseControllerName)) {
613
            @trigger_error(sprintf(
614
                'Passing other type than string or null as argument 3 for method %s() is deprecated since sonata-project/admin-bundle 3.65. It will accept only string and null in version 4.0.',
615
                __METHOD__
616
            ), E_USER_DEPRECATED);
617
        }
618
        $this->baseControllerName = $baseControllerName;
619
620
        // NEXT_MAJOR: Remove this line.
621
        $this->predefinePerPageOptions();
622
623
        // NEXT_MAJOR: Remove this line.
624
        $this->datagridValues['_per_page'] = $this->maxPerPage;
625
    }
626
627
    /**
628
     * {@inheritdoc}
629
     */
630
    public function getExportFormats()
631
    {
632
        return [
633
            'json', 'xml', 'csv', 'xls',
634
        ];
635
    }
636
637
    /**
638
     * @return array
639
     */
640
    public function getExportFields()
641
    {
642
        $fields = $this->getModelManager()->getExportFields($this->getClass());
643
644
        foreach ($this->getExtensions() as $extension) {
645
            if (method_exists($extension, 'configureExportFields')) {
646
                $fields = $extension->configureExportFields($this, $fields);
647
            }
648
        }
649
650
        return $fields;
651
    }
652
653
    public function getDataSourceIterator()
654
    {
655
        $datagrid = $this->getDatagrid();
656
        $datagrid->buildPager();
657
658
        $fields = [];
659
660
        foreach ($this->getExportFields() as $key => $field) {
661
            $label = $this->getTranslationLabel($field, 'export', 'label');
662
            $transLabel = $this->trans($label);
663
664
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
665
            // No translation key exists
666
            if ($transLabel === $label) {
667
                $fields[$key] = $field;
668
            } else {
669
                $fields[$transLabel] = $field;
670
            }
671
        }
672
673
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
674
    }
675
676
    public function validate(ErrorElement $errorElement, $object)
677
    {
678
    }
679
680
    /**
681
     * define custom variable.
682
     */
683
    public function initialize()
684
    {
685
        if (!$this->classnameLabel) {
686
            /* NEXT_MAJOR: remove cast to string, null is not supposed to be
687
            supported but was documented as such */
688
            $this->classnameLabel = substr(
689
                (string) $this->getClass(),
690
                strrpos((string) $this->getClass(), '\\') + 1
691
            );
692
        }
693
694
        // NEXT_MAJOR: Remove this line.
695
        $this->baseCodeRoute = $this->getCode();
696
697
        $this->configure();
698
    }
699
700
    public function configure()
701
    {
702
    }
703
704
    public function update($object)
705
    {
706
        $this->preUpdate($object);
707
        foreach ($this->extensions as $extension) {
708
            $extension->preUpdate($this, $object);
709
        }
710
711
        $result = $this->getModelManager()->update($object);
712
        // BC compatibility
713
        if (null !== $result) {
714
            $object = $result;
715
        }
716
717
        $this->postUpdate($object);
718
        foreach ($this->extensions as $extension) {
719
            $extension->postUpdate($this, $object);
720
        }
721
722
        return $object;
723
    }
724
725
    public function create($object)
726
    {
727
        $this->prePersist($object);
728
        foreach ($this->extensions as $extension) {
729
            $extension->prePersist($this, $object);
730
        }
731
732
        $result = $this->getModelManager()->create($object);
733
        // BC compatibility
734
        if (null !== $result) {
735
            $object = $result;
736
        }
737
738
        $this->postPersist($object);
739
        foreach ($this->extensions as $extension) {
740
            $extension->postPersist($this, $object);
741
        }
742
743
        $this->createObjectSecurity($object);
744
745
        return $object;
746
    }
747
748
    public function delete($object)
749
    {
750
        $this->preRemove($object);
751
        foreach ($this->extensions as $extension) {
752
            $extension->preRemove($this, $object);
753
        }
754
755
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
756
        $this->getModelManager()->delete($object);
757
758
        $this->postRemove($object);
759
        foreach ($this->extensions as $extension) {
760
            $extension->postRemove($this, $object);
761
        }
762
    }
763
764
    /**
765
     * @param object $object
766
     */
767
    public function preValidate($object)
768
    {
769
    }
770
771
    public function preUpdate($object)
772
    {
773
    }
774
775
    public function postUpdate($object)
776
    {
777
    }
778
779
    public function prePersist($object)
780
    {
781
    }
782
783
    public function postPersist($object)
784
    {
785
    }
786
787
    public function preRemove($object)
788
    {
789
    }
790
791
    public function postRemove($object)
792
    {
793
    }
794
795
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
796
    {
797
    }
798
799
    public function getFilterParameters()
800
    {
801
        $parameters = [];
802
803
        // build the values array
804
        if ($this->hasRequest()) {
805
            $filters = $this->request->query->get('filter', []);
806
            if (isset($filters['_page'])) {
807
                $filters['_page'] = (int) $filters['_page'];
808
            }
809
            if (isset($filters['_per_page'])) {
810
                $filters['_per_page'] = (int) $filters['_per_page'];
811
            }
812
813
            // if filter persistence is configured
814
            // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
815
            if (false !== $this->persistFilters && null !== $this->filterPersister) {
816
                // if reset filters is asked, remove from storage
817
                if ('reset' === $this->request->query->get('filters')) {
818
                    $this->filterPersister->reset($this->getCode());
819
                }
820
821
                // if no filters, fetch from storage
822
                // otherwise save to storage
823
                if (empty($filters)) {
824
                    $filters = $this->filterPersister->get($this->getCode());
825
                } else {
826
                    $this->filterPersister->set($this->getCode(), $filters);
827
                }
828
            }
829
830
            $parameters = array_merge(
831
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
832
                $this->datagridValues, // NEXT_MAJOR: Remove this line.
833
                $this->getDefaultSortValues(),
834
                $this->getDefaultFilterValues(),
835
                $filters
836
            );
837
838
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
839
                $parameters['_per_page'] = $this->getMaxPerPage();
840
            }
841
842
            // always force the parent value
843
            if ($this->isChild() && $this->getParentAssociationMapping()) {
844
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
845
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
846
            }
847
        }
848
849
        return $parameters;
850
    }
851
852
    /**
853
     * NEXT_MAJOR: Change the visibility to protected (similar to buildShow, buildForm, ...).
854
     */
855
    public function buildDatagrid()
856
    {
857
        if ($this->loaded['datagrid']) {
858
            return;
859
        }
860
861
        $this->loaded['datagrid'] = true;
862
863
        $filterParameters = $this->getFilterParameters();
864
865
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
866
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
867
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
868
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
869
            } else {
870
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
871
                    $this->getClass(),
872
                    $filterParameters['_sort_by'],
873
                    []
874
                );
875
876
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
877
            }
878
        }
879
880
        // initialize the datagrid
881
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
882
883
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
884
885
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
886
887
        // build the datagrid filter
888
        $this->configureDatagridFilters($mapper);
889
890
        // ok, try to limit to add parent filter
891
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
892
            $mapper->add($this->getParentAssociationMapping(), null, [
893
                'show_filter' => false,
894
                'label' => false,
895
                'field_type' => ModelHiddenType::class,
896
                'field_options' => [
897
                    'model_manager' => $this->getModelManager(),
898
                ],
899
                'operator_type' => HiddenType::class,
900
            ], null, null, [
901
                'admin_code' => $this->getParent()->getCode(),
902
            ]);
903
        }
904
905
        foreach ($this->getExtensions() as $extension) {
906
            $extension->configureDatagridFilters($mapper);
907
        }
908
    }
909
910
    /**
911
     * Returns the name of the parent related field, so the field can be use to set the default
912
     * value (ie the parent object) or to filter the object.
913
     *
914
     * @throws \InvalidArgumentException
915
     *
916
     * @return string|null
917
     */
918
    public function getParentAssociationMapping()
919
    {
920
        // NEXT_MAJOR: remove array check
921
        if (\is_array($this->parentAssociationMapping) && $this->isChild()) {
922
            $parent = $this->getParent()->getCode();
923
924
            if (\array_key_exists($parent, $this->parentAssociationMapping)) {
925
                return $this->parentAssociationMapping[$parent];
926
            }
927
928
            throw new \InvalidArgumentException(sprintf(
929
                "There's no association between %s and %s.",
930
                $this->getCode(),
931
                $this->getParent()->getCode()
932
            ));
933
        }
934
935
        // NEXT_MAJOR: remove this line
936
        return $this->parentAssociationMapping;
937
    }
938
939
    /**
940
     * @param string $code
941
     * @param string $value
942
     */
943
    final public function addParentAssociationMapping($code, $value)
944
    {
945
        $this->parentAssociationMapping[$code] = $value;
946
    }
947
948
    /**
949
     * Returns the baseRoutePattern used to generate the routing information.
950
     *
951
     * @throws \RuntimeException
952
     *
953
     * @return string the baseRoutePattern used to generate the routing information
954
     */
955
    public function getBaseRoutePattern()
956
    {
957
        if (null !== $this->cachedBaseRoutePattern) {
958
            return $this->cachedBaseRoutePattern;
959
        }
960
961
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
962
            $baseRoutePattern = $this->baseRoutePattern;
963
            if (!$this->baseRoutePattern) {
964
                preg_match(self::CLASS_REGEX, $this->class, $matches);
965
966
                if (!$matches) {
967
                    throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', static::class));
968
                }
969
                $baseRoutePattern = $this->urlize($matches[5], '-');
970
            }
971
972
            $this->cachedBaseRoutePattern = sprintf(
973
                '%s/%s/%s',
974
                $this->getParent()->getBaseRoutePattern(),
975
                $this->getParent()->getRouterIdParameter(),
976
                $baseRoutePattern
977
            );
978
        } elseif ($this->baseRoutePattern) {
979
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
980
        } else {
981
            preg_match(self::CLASS_REGEX, $this->class, $matches);
982
983
            if (!$matches) {
984
                throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', static::class));
985
            }
986
987
            $this->cachedBaseRoutePattern = sprintf(
988
                '/%s%s/%s',
989
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
990
                $this->urlize($matches[3], '-'),
991
                $this->urlize($matches[5], '-')
992
            );
993
        }
994
995
        return $this->cachedBaseRoutePattern;
996
    }
997
998
    /**
999
     * Returns the baseRouteName used to generate the routing information.
1000
     *
1001
     * @throws \RuntimeException
1002
     *
1003
     * @return string the baseRouteName used to generate the routing information
1004
     */
1005
    public function getBaseRouteName()
1006
    {
1007
        if (null !== $this->cachedBaseRouteName) {
1008
            return $this->cachedBaseRouteName;
1009
        }
1010
1011
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
1012
            $baseRouteName = $this->baseRouteName;
1013
            if (!$this->baseRouteName) {
1014
                preg_match(self::CLASS_REGEX, $this->class, $matches);
1015
1016
                if (!$matches) {
1017
                    throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', static::class));
1018
                }
1019
                $baseRouteName = $this->urlize($matches[5]);
1020
            }
1021
1022
            $this->cachedBaseRouteName = sprintf(
1023
                '%s_%s',
1024
                $this->getParent()->getBaseRouteName(),
1025
                $baseRouteName
1026
            );
1027
        } elseif ($this->baseRouteName) {
1028
            $this->cachedBaseRouteName = $this->baseRouteName;
1029
        } else {
1030
            preg_match(self::CLASS_REGEX, $this->class, $matches);
1031
1032
            if (!$matches) {
1033
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', static::class));
1034
            }
1035
1036
            $this->cachedBaseRouteName = sprintf(
1037
                'admin_%s%s_%s',
1038
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
1039
                $this->urlize($matches[3]),
1040
                $this->urlize($matches[5])
1041
            );
1042
        }
1043
1044
        return $this->cachedBaseRouteName;
1045
    }
1046
1047
    /**
1048
     * urlize the given word.
1049
     *
1050
     * @param string $word
1051
     * @param string $sep  the separator
1052
     *
1053
     * @return string
1054
     */
1055
    public function urlize($word, $sep = '_')
1056
    {
1057
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
1058
    }
1059
1060
    public function getClass()
1061
    {
1062
        if ($this->hasActiveSubClass()) {
1063
            if ($this->hasParentFieldDescription()) {
1064
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
1065
            }
1066
1067
            $subClass = $this->getRequest()->query->get('subclass');
1068
1069
            if (!$this->hasSubClass($subClass)) {
1070
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
1071
            }
1072
1073
            return $this->getSubClass($subClass);
1074
        }
1075
1076
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
1077
        if ($this->subject && \is_object($this->subject)) {
1078
            return ClassUtils::getClass($this->subject);
1079
        }
1080
1081
        return $this->class;
1082
    }
1083
1084
    public function getSubClasses()
1085
    {
1086
        return $this->subClasses;
1087
    }
1088
1089
    /**
1090
     * NEXT_MAJOR: remove this method.
1091
     */
1092
    public function addSubClass($subClass)
1093
    {
1094
        @trigger_error(sprintf(
1095
            'Method "%s" is deprecated since sonata-project/admin-bundle 3.30 and will be removed in 4.0.',
1096
            __METHOD__
1097
        ), E_USER_DEPRECATED);
1098
1099
        if (!\in_array($subClass, $this->subClasses, true)) {
1100
            $this->subClasses[] = $subClass;
1101
        }
1102
    }
1103
1104
    public function setSubClasses(array $subClasses)
1105
    {
1106
        $this->subClasses = $subClasses;
1107
    }
1108
1109
    public function hasSubClass($name)
1110
    {
1111
        return isset($this->subClasses[$name]);
1112
    }
1113
1114
    public function hasActiveSubClass()
1115
    {
1116
        if (\count($this->subClasses) > 0 && $this->request) {
1117
            return null !== $this->getRequest()->query->get('subclass');
1118
        }
1119
1120
        return false;
1121
    }
1122
1123
    public function getActiveSubClass()
1124
    {
1125
        if (!$this->hasActiveSubClass()) {
1126
            @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...
1127
                'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52 and will throw an exception in 4.0. '.
1128
                'Use %s::hasActiveSubClass() to know if there is an active subclass.',
1129
                __METHOD__,
1130
                __CLASS__
1131
            ), E_USER_DEPRECATED);
1132
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1133
            // throw new \LogicException(sprintf(
1134
            //    'Admin "%s" has no active subclass.',
1135
            //    static::class
1136
            // ));
1137
1138
            return null;
1139
        }
1140
1141
        return $this->getSubClass($this->getActiveSubclassCode());
1142
    }
1143
1144
    public function getActiveSubclassCode()
1145
    {
1146
        if (!$this->hasActiveSubClass()) {
1147
            @trigger_error(sprintf(
1148
                'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52 and will throw an exception in 4.0. '.
1149
                'Use %s::hasActiveSubClass() to know if there is an active subclass.',
1150
                __METHOD__,
1151
                __CLASS__
1152
            ), E_USER_DEPRECATED);
1153
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1154
            // throw new \LogicException(sprintf(
1155
            //    'Admin "%s" has no active subclass.',
1156
            //    static::class
1157
            // ));
1158
1159
            return null;
1160
        }
1161
1162
        $subClass = $this->getRequest()->query->get('subclass');
1163
1164
        if (!$this->hasSubClass($subClass)) {
1165
            @trigger_error(sprintf(
1166
                'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52 and will throw an exception in 4.0. '.
1167
                'Use %s::hasActiveSubClass() to know if there is an active subclass.',
1168
                __METHOD__,
1169
                __CLASS__
1170
            ), E_USER_DEPRECATED);
1171
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1172
            // throw new \LogicException(sprintf(
1173
            //    'Admin "%s" has no active subclass.',
1174
            //    static::class
1175
            // ));
1176
1177
            return null;
1178
        }
1179
1180
        return $subClass;
1181
    }
1182
1183
    public function getBatchActions()
1184
    {
1185
        $actions = [];
1186
1187
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1188
            $actions['delete'] = [
1189
                'label' => 'action_delete',
1190
                'translation_domain' => 'SonataAdminBundle',
1191
                'ask_confirmation' => true, // by default always true
1192
            ];
1193
        }
1194
1195
        $actions = $this->configureBatchActions($actions);
1196
1197
        foreach ($this->getExtensions() as $extension) {
1198
            // NEXT_MAJOR: remove method check
1199
            if (method_exists($extension, 'configureBatchActions')) {
1200
                $actions = $extension->configureBatchActions($this, $actions);
1201
            }
1202
        }
1203
1204
        foreach ($actions  as $name => &$action) {
1205
            if (!\array_key_exists('label', $action)) {
1206
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1207
            }
1208
1209
            if (!\array_key_exists('translation_domain', $action)) {
1210
                $action['translation_domain'] = $this->getTranslationDomain();
1211
            }
1212
        }
1213
1214
        return $actions;
1215
    }
1216
1217
    public function getRoutes()
1218
    {
1219
        $this->buildRoutes();
1220
1221
        return $this->routes;
1222
    }
1223
1224
    public function getRouterIdParameter()
1225
    {
1226
        return '{'.$this->getIdParameter().'}';
1227
    }
1228
1229
    public function getIdParameter()
1230
    {
1231
        $parameter = 'id';
1232
1233
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1234
            $parameter = 'child'.ucfirst($parameter);
1235
        }
1236
1237
        return $parameter;
1238
    }
1239
1240
    public function hasRoute($name)
1241
    {
1242
        if (!$this->routeGenerator) {
1243
            throw new \RuntimeException('RouteGenerator cannot be null');
1244
        }
1245
1246
        return $this->routeGenerator->hasAdminRoute($this, $name);
1247
    }
1248
1249
    /**
1250
     * @param string      $name
1251
     * @param string|null $adminCode
1252
     *
1253
     * @return bool
1254
     */
1255
    public function isCurrentRoute($name, $adminCode = null)
1256
    {
1257
        if (!$this->hasRequest()) {
1258
            return false;
1259
        }
1260
1261
        $request = $this->getRequest();
1262
        $route = $request->get('_route');
1263
1264
        if ($adminCode) {
1265
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1266
        } else {
1267
            $admin = $this;
1268
        }
1269
1270
        if (!$admin) {
1271
            return false;
1272
        }
1273
1274
        return ($admin->getBaseRouteName().'_'.$name) === $route;
1275
    }
1276
1277
    public function generateObjectUrl($name, $object, array $parameters = [], $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1278
    {
1279
        $parameters['id'] = $this->getUrlSafeIdentifier($object);
1280
1281
        return $this->generateUrl($name, $parameters, $referenceType);
1282
    }
1283
1284
    public function generateUrl($name, array $parameters = [], $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1285
    {
1286
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $referenceType);
1287
    }
1288
1289
    public function generateMenuUrl($name, array $parameters = [], $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1290
    {
1291
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $referenceType);
1292
    }
1293
1294
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry)
1295
    {
1296
        $this->templateRegistry = $templateRegistry;
1297
    }
1298
1299
    /**
1300
     * @param array<string, string> $templates
1301
     */
1302
    public function setTemplates(array $templates)
1303
    {
1304
        // NEXT_MAJOR: Remove this line
1305
        $this->templates = $templates;
1306
1307
        $this->getTemplateRegistry()->setTemplates($templates);
1308
    }
1309
1310
    /**
1311
     * @param string $name
1312
     * @param string $template
1313
     */
1314
    public function setTemplate($name, $template)
1315
    {
1316
        // NEXT_MAJOR: Remove this line
1317
        $this->templates[$name] = $template;
1318
1319
        $this->getTemplateRegistry()->setTemplate($name, $template);
1320
    }
1321
1322
    /**
1323
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1324
     *
1325
     * @return array<string, string>
1326
     */
1327
    public function getTemplates()
1328
    {
1329
        return $this->getTemplateRegistry()->getTemplates();
1330
    }
1331
1332
    /**
1333
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1334
     *
1335
     * @param string $name
1336
     *
1337
     * @return string|null
1338
     */
1339
    public function getTemplate($name)
1340
    {
1341
        return $this->getTemplateRegistry()->getTemplate($name);
1342
    }
1343
1344
    public function getNewInstance()
1345
    {
1346
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1347
        foreach ($this->getExtensions() as $extension) {
1348
            $extension->alterNewInstance($this, $object);
1349
        }
1350
1351
        return $object;
1352
    }
1353
1354
    public function getFormBuilder()
1355
    {
1356
        $this->formOptions['data_class'] = $this->getClass();
1357
1358
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1359
            $this->getUniqid(),
1360
            $this->formOptions
1361
        );
1362
1363
        $this->defineFormBuilder($formBuilder);
1364
1365
        return $formBuilder;
1366
    }
1367
1368
    /**
1369
     * This method is being called by the main admin class and the child class,
1370
     * the getFormBuilder is only call by the main admin class.
1371
     */
1372
    public function defineFormBuilder(FormBuilderInterface $formBuilder)
1373
    {
1374
        if (!$this->hasSubject()) {
1375
            @trigger_error(sprintf(
1376
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.65 and will throw an exception in 4.0. '.
1377
                'Use %s::setSubject() to set the subject.',
1378
                __METHOD__,
1379
                __CLASS__
1380
            ), E_USER_DEPRECATED);
1381
            // NEXT_MAJOR : remove the previous `trigger_error()` call and uncomment the following exception
1382
            // throw new \LogicException(sprintf(
1383
            //    'Admin "%s" has no subject.',
1384
            //    static::class
1385
            // ));
1386
        }
1387
1388
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1389
1390
        $this->configureFormFields($mapper);
1391
1392
        foreach ($this->getExtensions() as $extension) {
1393
            $extension->configureFormFields($mapper);
1394
        }
1395
1396
        $this->attachInlineValidator();
1397
    }
1398
1399
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
1400
    {
1401
        $pool = $this->getConfigurationPool();
1402
1403
        $adminCode = $fieldDescription->getOption('admin_code');
1404
1405
        if (null !== $adminCode) {
1406
            if (!$pool->hasAdminByAdminCode($adminCode)) {
1407
                return;
1408
            }
1409
1410
            $admin = $pool->getAdminByAdminCode($adminCode);
1411
        } else {
1412
            if (!$pool->hasAdminByClass($fieldDescription->getTargetEntity())) {
1413
                return;
1414
            }
1415
1416
            $admin = $pool->getAdminByClass($fieldDescription->getTargetEntity());
1417
        }
1418
1419
        if ($this->hasRequest()) {
1420
            $admin->setRequest($this->getRequest());
1421
        }
1422
1423
        $fieldDescription->setAssociationAdmin($admin);
1424
    }
1425
1426
    public function getObject($id)
1427
    {
1428
        $object = $this->getModelManager()->find($this->getClass(), $id);
1429
        foreach ($this->getExtensions() as $extension) {
1430
            $extension->alterObject($this, $object);
1431
        }
1432
1433
        return $object;
1434
    }
1435
1436
    public function getForm()
1437
    {
1438
        $this->buildForm();
1439
1440
        return $this->form;
1441
    }
1442
1443
    public function getList()
1444
    {
1445
        $this->buildList();
1446
1447
        return $this->list;
1448
    }
1449
1450
    /**
1451
     * @final since sonata-project/admin-bundle 3.63.0
1452
     */
1453
    public function createQuery($context = 'list')
1454
    {
1455
        if (\func_num_args() > 0) {
1456
            @trigger_error(
1457
                'The $context argument of '.__METHOD__.' is deprecated since 3.3, to be removed in 4.0.',
1458
                E_USER_DEPRECATED
1459
            );
1460
        }
1461
1462
        $query = $this->getModelManager()->createQuery($this->getClass());
1463
1464
        $query = $this->configureQuery($query);
1465
        foreach ($this->extensions as $extension) {
1466
            $extension->configureQuery($this, $query, $context);
1467
        }
1468
1469
        return $query;
1470
    }
1471
1472
    public function getDatagrid()
1473
    {
1474
        $this->buildDatagrid();
1475
1476
        return $this->datagrid;
1477
    }
1478
1479
    public function buildTabMenu($action, ?AdminInterface $childAdmin = null)
1480
    {
1481
        if ($this->loaded['tab_menu']) {
1482
            return $this->menu;
1483
        }
1484
1485
        $this->loaded['tab_menu'] = true;
1486
1487
        $menu = $this->menuFactory->createItem('root');
1488
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1489
        $menu->setExtra('translation_domain', $this->translationDomain);
1490
1491
        // Prevents BC break with KnpMenuBundle v1.x
1492
        if (method_exists($menu, 'setCurrentUri')) {
1493
            $menu->setCurrentUri($this->getRequest()->getBaseUrl().$this->getRequest()->getPathInfo());
1494
        }
1495
1496
        $this->configureTabMenu($menu, $action, $childAdmin);
1497
1498
        foreach ($this->getExtensions() as $extension) {
1499
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1500
        }
1501
1502
        $this->menu = $menu;
1503
1504
        return $this->menu;
1505
    }
1506
1507
    public function buildSideMenu($action, ?AdminInterface $childAdmin = null)
1508
    {
1509
        return $this->buildTabMenu($action, $childAdmin);
1510
    }
1511
1512
    /**
1513
     * @param string $action
1514
     *
1515
     * @return ItemInterface
1516
     */
1517
    public function getSideMenu($action, ?AdminInterface $childAdmin = null)
1518
    {
1519
        if ($this->isChild()) {
1520
            return $this->getParent()->getSideMenu($action, $this);
1521
        }
1522
1523
        $this->buildSideMenu($action, $childAdmin);
1524
1525
        return $this->menu;
1526
    }
1527
1528
    /**
1529
     * Returns the root code.
1530
     *
1531
     * @return string the root code
1532
     */
1533
    public function getRootCode()
1534
    {
1535
        return $this->getRoot()->getCode();
1536
    }
1537
1538
    /**
1539
     * Returns the master admin.
1540
     *
1541
     * @return AbstractAdmin the root admin class
1542
     */
1543
    public function getRoot()
1544
    {
1545
        if (!$this->hasParentFieldDescription()) {
1546
            return $this;
1547
        }
1548
1549
        return $this->getParentFieldDescription()->getAdmin()->getRoot();
1550
    }
1551
1552
    public function setBaseControllerName($baseControllerName)
1553
    {
1554
        $this->baseControllerName = $baseControllerName;
1555
    }
1556
1557
    public function getBaseControllerName()
1558
    {
1559
        return $this->baseControllerName;
1560
    }
1561
1562
    /**
1563
     * @param string $label
1564
     */
1565
    public function setLabel($label)
1566
    {
1567
        $this->label = $label;
1568
    }
1569
1570
    public function getLabel()
1571
    {
1572
        return $this->label;
1573
    }
1574
1575
    /**
1576
     * @param bool $persist
1577
     *
1578
     * NEXT_MAJOR: remove this method
1579
     *
1580
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
1581
     */
1582
    public function setPersistFilters($persist)
1583
    {
1584
        @trigger_error(
1585
            'The '.__METHOD__.' method is deprecated since version 3.34 and will be removed in 4.0.',
1586
            E_USER_DEPRECATED
1587
        );
1588
1589
        $this->persistFilters = $persist;
1590
    }
1591
1592
    public function setFilterPersister(?FilterPersisterInterface $filterPersister = null)
1593
    {
1594
        $this->filterPersister = $filterPersister;
1595
        // NEXT_MAJOR remove the deprecated property will be removed. Needed for persisted filter condition.
1596
        $this->persistFilters = true;
1597
    }
1598
1599
    /**
1600
     * NEXT_MAJOR: Remove this method.
1601
     *
1602
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
1603
     *
1604
     * @param int $maxPerPage
1605
     */
1606
    public function setMaxPerPage($maxPerPage)
1607
    {
1608
        @trigger_error(sprintf(
1609
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
1610
            __METHOD__
1611
        ), E_USER_DEPRECATED);
1612
1613
        $this->maxPerPage = $maxPerPage;
1614
    }
1615
1616
    /**
1617
     * @return int
1618
     */
1619
    public function getMaxPerPage()
1620
    {
1621
        // NEXT_MAJOR: Remove this line and uncomment the following.
1622
        return $this->maxPerPage;
1623
        // $sortValues = $this->getModelManager()->getDefaultSortValues($this->class);
1624
1625
        // return $sortValues['_per_page'] ?? 25;
1626
    }
1627
1628
    /**
1629
     * @param int $maxPageLinks
1630
     */
1631
    public function setMaxPageLinks($maxPageLinks)
1632
    {
1633
        $this->maxPageLinks = $maxPageLinks;
1634
    }
1635
1636
    /**
1637
     * @return int
1638
     */
1639
    public function getMaxPageLinks()
1640
    {
1641
        return $this->maxPageLinks;
1642
    }
1643
1644
    public function getFormGroups()
1645
    {
1646
        if (!\is_array($this->formGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1647
            @trigger_error(sprintf(
1648
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65. It will return only array in version 4.0.',
1649
                __METHOD__
1650
            ), E_USER_DEPRECATED);
1651
        }
1652
1653
        return $this->formGroups;
1654
    }
1655
1656
    public function setFormGroups(array $formGroups)
1657
    {
1658
        $this->formGroups = $formGroups;
1659
    }
1660
1661
    public function removeFieldFromFormGroup($key)
1662
    {
1663
        foreach ($this->formGroups as $name => $formGroup) {
1664
            unset($this->formGroups[$name]['fields'][$key]);
1665
1666
            if (empty($this->formGroups[$name]['fields'])) {
1667
                unset($this->formGroups[$name]);
1668
            }
1669
        }
1670
    }
1671
1672
    /**
1673
     * @param string $group
1674
     */
1675
    public function reorderFormGroup($group, array $keys)
1676
    {
1677
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1678
        $formGroups = $this->getFormGroups('sonata_deprecation_mute');
1679
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1680
        $this->setFormGroups($formGroups);
1681
    }
1682
1683
    public function getFormTabs()
1684
    {
1685
        if (!\is_array($this->formTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1686
            @trigger_error(sprintf(
1687
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65. It will return only array in version 4.0.',
1688
                __METHOD__
1689
            ), E_USER_DEPRECATED);
1690
        }
1691
1692
        return $this->formTabs;
1693
    }
1694
1695
    public function setFormTabs(array $formTabs)
1696
    {
1697
        $this->formTabs = $formTabs;
1698
    }
1699
1700
    public function getShowTabs()
1701
    {
1702
        if (!\is_array($this->showTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1703
            @trigger_error(sprintf(
1704
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65. It will return only array in version 4.0.',
1705
                __METHOD__
1706
            ), E_USER_DEPRECATED);
1707
        }
1708
1709
        return $this->showTabs;
1710
    }
1711
1712
    public function setShowTabs(array $showTabs)
1713
    {
1714
        $this->showTabs = $showTabs;
1715
    }
1716
1717
    public function getShowGroups()
1718
    {
1719
        if (!\is_array($this->showGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1720
            @trigger_error(sprintf(
1721
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65. It will return only array in version 4.0.',
1722
                __METHOD__
1723
            ), E_USER_DEPRECATED);
1724
        }
1725
1726
        return $this->showGroups;
1727
    }
1728
1729
    public function setShowGroups(array $showGroups)
1730
    {
1731
        $this->showGroups = $showGroups;
1732
    }
1733
1734
    public function reorderShowGroup($group, array $keys)
1735
    {
1736
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1737
        $showGroups = $this->getShowGroups('sonata_deprecation_mute');
1738
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1739
        $this->setShowGroups($showGroups);
1740
    }
1741
1742
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
1743
    {
1744
        $this->parentFieldDescription = $parentFieldDescription;
1745
    }
1746
1747
    public function getParentFieldDescription()
1748
    {
1749
        if (!$this->hasParentFieldDescription()) {
1750
            @trigger_error(sprintf(
1751
                'Calling %s() when there is no parent field description is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
1752
                'Use %s::hasParentFieldDescription() to know if there is a parent field description.',
1753
                __METHOD__,
1754
                __CLASS__
1755
            ), E_USER_DEPRECATED);
1756
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1757
            // throw new \LogicException(sprintf(
1758
            //    'Admin "%s" has no parent field description.',
1759
            //    static::class
1760
            // ));
1761
1762
            return null;
1763
        }
1764
1765
        return $this->parentFieldDescription;
1766
    }
1767
1768
    public function hasParentFieldDescription()
1769
    {
1770
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1771
    }
1772
1773
    public function setSubject($subject)
1774
    {
1775
        if (\is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1776
            $message = <<<'EOT'
1777
You are trying to set entity an instance of "%s",
1778
which is not the one registered with this admin class ("%s").
1779
This is deprecated since 3.5 and will no longer be supported in 4.0.
1780
EOT;
1781
1782
            @trigger_error(
1783
                sprintf($message, \get_class($subject), $this->getClass()),
1784
                E_USER_DEPRECATED
1785
            ); // NEXT_MAJOR : throw an exception instead
1786
        }
1787
1788
        $this->subject = $subject;
1789
    }
1790
1791
    public function getSubject()
1792
    {
1793
        if (!$this->hasSubject()) {
1794
            @trigger_error(sprintf(
1795
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
1796
                'Use %s::hasSubject() to know if there is a subject.',
1797
                __METHOD__,
1798
                __CLASS__
1799
            ), E_USER_DEPRECATED);
1800
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and update the return type
1801
            // throw new \LogicException(sprintf(
1802
            //    'Admin "%s" has no subject.',
1803
            //    static::class
1804
            // ));
1805
1806
            return null;
1807
        }
1808
1809
        return $this->subject;
1810
    }
1811
1812
    public function hasSubject()
1813
    {
1814
        if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
1815
            $id = $this->request->get($this->getIdParameter());
1816
1817
            if (null !== $id) {
1818
                $this->subject = $this->getObject($id);
1819
            }
1820
        }
1821
1822
        return null !== $this->subject;
1823
    }
1824
1825
    public function getFormFieldDescriptions()
1826
    {
1827
        $this->buildForm();
1828
1829
        return $this->formFieldDescriptions;
1830
    }
1831
1832
    public function getFormFieldDescription($name)
1833
    {
1834
        $this->buildForm();
1835
1836
        if (!$this->hasFormFieldDescription($name)) {
1837
            @trigger_error(sprintf(
1838
                'Calling %s() when there is no form field description is deprecated since sonata-project/admin-bundle 3.69 and will throw an exception in 4.0. '.
1839
                'Use %s::hasFormFieldDescription() to know if there is a form field description.',
1840
                __METHOD__,
1841
                __CLASS__
1842
            ), E_USER_DEPRECATED);
1843
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1844
            // throw new \LogicException(sprintf(
1845
            //    'Admin "%s" has no form field description for the field %s.',
1846
            //    static::class,
1847
            //    $name
1848
            // ));
1849
1850
            return null;
1851
        }
1852
1853
        return $this->formFieldDescriptions[$name];
1854
    }
1855
1856
    /**
1857
     * Returns true if the admin has a FieldDescription with the given $name.
1858
     *
1859
     * @param string $name
1860
     *
1861
     * @return bool
1862
     */
1863
    public function hasFormFieldDescription($name)
1864
    {
1865
        $this->buildForm();
1866
1867
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1868
    }
1869
1870
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1871
    {
1872
        $this->formFieldDescriptions[$name] = $fieldDescription;
1873
    }
1874
1875
    /**
1876
     * remove a FieldDescription.
1877
     *
1878
     * @param string $name
1879
     */
1880
    public function removeFormFieldDescription($name)
1881
    {
1882
        unset($this->formFieldDescriptions[$name]);
1883
    }
1884
1885
    /**
1886
     * build and return the collection of form FieldDescription.
1887
     *
1888
     * @return FieldDescriptionInterface[] collection of form FieldDescription
1889
     */
1890
    public function getShowFieldDescriptions()
1891
    {
1892
        $this->buildShow();
1893
1894
        return $this->showFieldDescriptions;
1895
    }
1896
1897
    /**
1898
     * Returns the form FieldDescription with the given $name.
1899
     *
1900
     * @param string $name
1901
     *
1902
     * @return FieldDescriptionInterface
1903
     */
1904
    public function getShowFieldDescription($name)
1905
    {
1906
        $this->buildShow();
1907
1908
        if (!$this->hasShowFieldDescription($name)) {
1909
            @trigger_error(sprintf(
1910
                'Calling %s() when there is no show field description is deprecated since sonata-project/admin-bundle 3.69 and will throw an exception in 4.0. '.
1911
                'Use %s::hasFormFieldDescription() to know if there is a show field description.',
1912
                __METHOD__,
1913
                __CLASS__
1914
            ), E_USER_DEPRECATED);
1915
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1916
            // throw new \LogicException(sprintf(
1917
            //    'Admin "%s" has no show field description for the field %s.',
1918
            //    static::class,
1919
            //    $name
1920
            // ));
1921
1922
            return null;
1923
        }
1924
1925
        return $this->showFieldDescriptions[$name];
1926
    }
1927
1928
    public function hasShowFieldDescription($name)
1929
    {
1930
        $this->buildShow();
1931
1932
        return \array_key_exists($name, $this->showFieldDescriptions);
1933
    }
1934
1935
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1936
    {
1937
        $this->showFieldDescriptions[$name] = $fieldDescription;
1938
    }
1939
1940
    public function removeShowFieldDescription($name)
1941
    {
1942
        unset($this->showFieldDescriptions[$name]);
1943
    }
1944
1945
    public function getListFieldDescriptions()
1946
    {
1947
        $this->buildList();
1948
1949
        return $this->listFieldDescriptions;
1950
    }
1951
1952
    public function getListFieldDescription($name)
1953
    {
1954
        $this->buildList();
1955
1956
        if (!$this->hasListFieldDescription($name)) {
1957
            @trigger_error(sprintf(
1958
                'Calling %s() when there is no list field description is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
1959
                'Use %s::hasListFieldDescription(\'%s\') to know if there is a list field description.',
1960
                __METHOD__,
1961
                __CLASS__,
1962
                $name
1963
            ), E_USER_DEPRECATED);
1964
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1965
            // throw new \LogicException(sprintf(
1966
            //    'Admin "%s" has no list field description for %s.',
1967
            //    static::class,
1968
            //    $name
1969
            // ));
1970
1971
            return null;
1972
        }
1973
1974
        return $this->listFieldDescriptions[$name];
1975
    }
1976
1977
    public function hasListFieldDescription($name)
1978
    {
1979
        $this->buildList();
1980
1981
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1982
    }
1983
1984
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1985
    {
1986
        $this->listFieldDescriptions[$name] = $fieldDescription;
1987
    }
1988
1989
    public function removeListFieldDescription($name)
1990
    {
1991
        unset($this->listFieldDescriptions[$name]);
1992
    }
1993
1994
    public function getFilterFieldDescription($name)
1995
    {
1996
        $this->buildDatagrid();
1997
1998
        if (!$this->hasFilterFieldDescription($name)) {
1999
            @trigger_error(sprintf(
2000
                'Calling %s() when there is no filter field description is deprecated since sonata-project/admin-bundle 3.69 and will throw an exception in 4.0. '.
2001
                'Use %s::hasFilterFieldDescription() to know if there is a filter field description.',
2002
                __METHOD__,
2003
                __CLASS__
2004
            ), E_USER_DEPRECATED);
2005
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
2006
            // throw new \LogicException(sprintf(
2007
            //    'Admin "%s" has no filter field description for the field %s.',
2008
            //    static::class,
2009
            //    $name
2010
            // ));
2011
2012
            return null;
2013
        }
2014
2015
        return $this->filterFieldDescriptions[$name];
2016
    }
2017
2018
    public function hasFilterFieldDescription($name)
2019
    {
2020
        $this->buildDatagrid();
2021
2022
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
2023
    }
2024
2025
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription)
2026
    {
2027
        $this->filterFieldDescriptions[$name] = $fieldDescription;
2028
    }
2029
2030
    public function removeFilterFieldDescription($name)
2031
    {
2032
        unset($this->filterFieldDescriptions[$name]);
2033
    }
2034
2035
    public function getFilterFieldDescriptions()
2036
    {
2037
        $this->buildDatagrid();
2038
2039
        return $this->filterFieldDescriptions;
2040
    }
2041
2042
    public function addChild(AdminInterface $child)
2043
    {
2044
        $parentAdmin = $this;
2045
        while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
2046
            $parentAdmin = $parentAdmin->getParent();
2047
        }
2048
2049
        if ($parentAdmin->getCode() === $child->getCode()) {
2050
            throw new \RuntimeException(sprintf(
2051
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
2052
                $child->getCode(),
2053
                $this->getCode()
2054
            ));
2055
        }
2056
2057
        $this->children[$child->getCode()] = $child;
2058
2059
        $child->setParent($this);
2060
2061
        // NEXT_MAJOR: remove $args and add $field parameter to this function on next Major
2062
2063
        $args = \func_get_args();
2064
2065
        if (isset($args[1])) {
2066
            $child->addParentAssociationMapping($this->getCode(), $args[1]);
2067
        } else {
2068
            @trigger_error(
2069
                'Calling "addChild" without second argument is deprecated since'
2070
                .' sonata-project/admin-bundle 3.35 and will not be allowed in 4.0.',
2071
                E_USER_DEPRECATED
2072
            );
2073
        }
2074
    }
2075
2076
    public function hasChild($code)
2077
    {
2078
        return isset($this->children[$code]);
2079
    }
2080
2081
    public function getChildren()
2082
    {
2083
        return $this->children;
2084
    }
2085
2086
    public function getChild($code)
2087
    {
2088
        if (!$this->hasChild($code)) {
2089
            @trigger_error(sprintf(
2090
                'Calling %s() when there is no child is deprecated since sonata-project/admin-bundle 3.69'
2091
                .' and will throw an exception in 4.0. Use %s::hasChild() to know if the child exists.',
2092
                __METHOD__,
2093
                __CLASS__
2094
            ), E_USER_DEPRECATED);
2095
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
2096
            // throw new \LogicException(sprintf(
2097
            //    'Admin "%s" has no child for the code %s.',
2098
            //    static::class,
2099
            //    $code
2100
            // ));
2101
2102
            return null;
2103
        }
2104
2105
        return $this->children[$code];
2106
    }
2107
2108
    public function setParent(AdminInterface $parent)
2109
    {
2110
        $this->parent = $parent;
2111
    }
2112
2113
    public function getParent()
2114
    {
2115
        if (!$this->isChild()) {
2116
            @trigger_error(sprintf(
2117
                'Calling %s() when there is no parent is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
2118
                'Use %s::isChild() to know if there is a parent.',
2119
                __METHOD__,
2120
                __CLASS__
2121
            ), E_USER_DEPRECATED);
2122
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
2123
            // throw new \LogicException(sprintf(
2124
            //    'Admin "%s" has no parent.',
2125
            //    static::class
2126
            // ));
2127
2128
            return null;
2129
        }
2130
2131
        return $this->parent;
2132
    }
2133
2134
    final public function getRootAncestor()
2135
    {
2136
        $parent = $this;
2137
2138
        while ($parent->isChild()) {
2139
            $parent = $parent->getParent();
2140
        }
2141
2142
        return $parent;
2143
    }
2144
2145
    final public function getChildDepth()
2146
    {
2147
        $parent = $this;
2148
        $depth = 0;
2149
2150
        while ($parent->isChild()) {
2151
            $parent = $parent->getParent();
2152
            ++$depth;
2153
        }
2154
2155
        return $depth;
2156
    }
2157
2158
    final public function getCurrentLeafChildAdmin()
2159
    {
2160
        $child = $this->getCurrentChildAdmin();
2161
2162
        if (null === $child) {
2163
            return null;
2164
        }
2165
2166
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
2167
            $child = $c;
2168
        }
2169
2170
        return $child;
2171
    }
2172
2173
    public function isChild()
2174
    {
2175
        return $this->parent instanceof AdminInterface;
2176
    }
2177
2178
    /**
2179
     * Returns true if the admin has children, false otherwise.
2180
     *
2181
     * @return bool if the admin has children
2182
     */
2183
    public function hasChildren()
2184
    {
2185
        return \count($this->children) > 0;
2186
    }
2187
2188
    public function setUniqid($uniqid)
2189
    {
2190
        $this->uniqid = $uniqid;
2191
    }
2192
2193
    public function getUniqid()
2194
    {
2195
        if (!$this->uniqid) {
2196
            $this->uniqid = 's'.uniqid();
2197
        }
2198
2199
        return $this->uniqid;
2200
    }
2201
2202
    /**
2203
     * Returns the classname label.
2204
     *
2205
     * @return string the classname label
2206
     */
2207
    public function getClassnameLabel()
2208
    {
2209
        return $this->classnameLabel;
2210
    }
2211
2212
    public function getPersistentParameters()
2213
    {
2214
        $parameters = [];
2215
2216
        foreach ($this->getExtensions() as $extension) {
2217
            $params = $extension->getPersistentParameters($this);
2218
2219
            if (!\is_array($params)) {
2220
                throw new \RuntimeException(sprintf('The %s::getPersistentParameters must return an array', \get_class($extension)));
2221
            }
2222
2223
            $parameters = array_merge($parameters, $params);
2224
        }
2225
2226
        return $parameters;
2227
    }
2228
2229
    /**
2230
     * @param string $name
2231
     *
2232
     * @return mixed|null
2233
     */
2234
    public function getPersistentParameter($name)
2235
    {
2236
        $parameters = $this->getPersistentParameters();
2237
2238
        return $parameters[$name] ?? null;
2239
    }
2240
2241
    public function getBreadcrumbs($action)
2242
    {
2243
        @trigger_error(
2244
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2245
            ' Use Sonata\AdminBundle\Admin\BreadcrumbsBuilder::getBreadcrumbs instead.',
2246
            E_USER_DEPRECATED
2247
        );
2248
2249
        return $this->getBreadcrumbsBuilder()->getBreadcrumbs($this, $action);
2250
    }
2251
2252
    /**
2253
     * Generates the breadcrumbs array.
2254
     *
2255
     * Note: the method will be called by the top admin instance (parent => child)
2256
     *
2257
     * @param string $action
2258
     *
2259
     * @return array
2260
     */
2261
    public function buildBreadcrumbs($action, ?ItemInterface $menu = null)
2262
    {
2263
        @trigger_error(
2264
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.',
2265
            E_USER_DEPRECATED
2266
        );
2267
2268
        if (isset($this->breadcrumbs[$action])) {
2269
            return $this->breadcrumbs[$action];
2270
        }
2271
2272
        return $this->breadcrumbs[$action] = $this->getBreadcrumbsBuilder()
2273
            ->buildBreadcrumbs($this, $action, $menu);
2274
    }
2275
2276
    /**
2277
     * NEXT_MAJOR : remove this method.
2278
     *
2279
     * @return BreadcrumbsBuilderInterface
2280
     */
2281
    final public function getBreadcrumbsBuilder()
2282
    {
2283
        @trigger_error(
2284
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2285
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
2286
            E_USER_DEPRECATED
2287
        );
2288
        if (null === $this->breadcrumbsBuilder) {
2289
            $this->breadcrumbsBuilder = new BreadcrumbsBuilder(
2290
                $this->getConfigurationPool()->getContainer()->getParameter('sonata.admin.configuration.breadcrumbs')
2291
            );
2292
        }
2293
2294
        return $this->breadcrumbsBuilder;
2295
    }
2296
2297
    /**
2298
     * NEXT_MAJOR : remove this method.
2299
     *
2300
     * @return AbstractAdmin
2301
     */
2302
    final public function setBreadcrumbsBuilder(BreadcrumbsBuilderInterface $value)
2303
    {
2304
        @trigger_error(
2305
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2306
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
2307
            E_USER_DEPRECATED
2308
        );
2309
        $this->breadcrumbsBuilder = $value;
2310
2311
        return $this;
2312
    }
2313
2314
    public function setCurrentChild($currentChild)
2315
    {
2316
        $this->currentChild = $currentChild;
2317
    }
2318
2319
    /**
2320
     * NEXT_MAJOR: Remove this method.
2321
     *
2322
     * @deprecated since sonata-project/admin-bundle 3.65, to be removed in 4.0
2323
     */
2324
    public function getCurrentChild()
2325
    {
2326
        @trigger_error(
2327
            sprintf(
2328
                'The %s() method is deprecated since version 3.65 and will be removed in 4.0. Use %s::isCurrentChild() instead.',
2329
                __METHOD__,
2330
                __CLASS__
2331
            ),
2332
            E_USER_DEPRECATED
2333
        );
2334
2335
        return $this->currentChild;
2336
    }
2337
2338
    public function isCurrentChild(): bool
2339
    {
2340
        return $this->currentChild;
2341
    }
2342
2343
    /**
2344
     * Returns the current child admin instance.
2345
     *
2346
     * @return AdminInterface|null the current child admin instance
2347
     */
2348
    public function getCurrentChildAdmin()
2349
    {
2350
        foreach ($this->children as $children) {
2351
            if ($children->isCurrentChild()) {
2352
                return $children;
2353
            }
2354
        }
2355
2356
        return null;
2357
    }
2358
2359
    public function trans($id, array $parameters = [], $domain = null, $locale = null)
2360
    {
2361
        @trigger_error(
2362
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2363
            E_USER_DEPRECATED
2364
        );
2365
2366
        $domain = $domain ?: $this->getTranslationDomain();
2367
2368
        return $this->translator->trans($id, $parameters, $domain, $locale);
2369
    }
2370
2371
    /**
2372
     * Translate a message id.
2373
     *
2374
     * NEXT_MAJOR: remove this method
2375
     *
2376
     * @param string      $id
2377
     * @param int         $count
2378
     * @param string|null $domain
2379
     * @param string|null $locale
2380
     *
2381
     * @return string the translated string
2382
     *
2383
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2384
     */
2385
    public function transChoice($id, $count, array $parameters = [], $domain = null, $locale = null)
2386
    {
2387
        @trigger_error(
2388
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2389
            E_USER_DEPRECATED
2390
        );
2391
2392
        $domain = $domain ?: $this->getTranslationDomain();
2393
2394
        return $this->translator->transChoice($id, $count, $parameters, $domain, $locale);
2395
    }
2396
2397
    public function setTranslationDomain($translationDomain)
2398
    {
2399
        $this->translationDomain = $translationDomain;
2400
    }
2401
2402
    public function getTranslationDomain()
2403
    {
2404
        return $this->translationDomain;
2405
    }
2406
2407
    /**
2408
     * {@inheritdoc}
2409
     *
2410
     * NEXT_MAJOR: remove this method
2411
     *
2412
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2413
     */
2414
    public function setTranslator(TranslatorInterface $translator)
2415
    {
2416
        $args = \func_get_args();
2417
        if (isset($args[1]) && $args[1]) {
2418
            @trigger_error(
2419
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2420
                E_USER_DEPRECATED
2421
            );
2422
        }
2423
2424
        $this->translator = $translator;
2425
    }
2426
2427
    /**
2428
     * {@inheritdoc}
2429
     *
2430
     * NEXT_MAJOR: remove this method
2431
     *
2432
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2433
     */
2434
    public function getTranslator()
2435
    {
2436
        @trigger_error(
2437
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2438
            E_USER_DEPRECATED
2439
        );
2440
2441
        return $this->translator;
2442
    }
2443
2444
    public function getTranslationLabel($label, $context = '', $type = '')
2445
    {
2446
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2447
    }
2448
2449
    public function setRequest(Request $request)
2450
    {
2451
        $this->request = $request;
2452
2453
        foreach ($this->getChildren() as $children) {
2454
            $children->setRequest($request);
2455
        }
2456
    }
2457
2458
    public function getRequest()
2459
    {
2460
        if (!$this->request) {
2461
            // NEXT_MAJOR: Throw \LogicException instead.
2462
            throw new \RuntimeException('The Request object has not been set');
2463
        }
2464
2465
        return $this->request;
2466
    }
2467
2468
    public function hasRequest()
2469
    {
2470
        return null !== $this->request;
2471
    }
2472
2473
    public function setFormContractor(FormContractorInterface $formBuilder)
2474
    {
2475
        $this->formContractor = $formBuilder;
2476
    }
2477
2478
    /**
2479
     * @return FormContractorInterface
2480
     */
2481
    public function getFormContractor()
2482
    {
2483
        return $this->formContractor;
2484
    }
2485
2486
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder)
2487
    {
2488
        $this->datagridBuilder = $datagridBuilder;
2489
    }
2490
2491
    public function getDatagridBuilder()
2492
    {
2493
        return $this->datagridBuilder;
2494
    }
2495
2496
    public function setListBuilder(ListBuilderInterface $listBuilder)
2497
    {
2498
        $this->listBuilder = $listBuilder;
2499
    }
2500
2501
    public function getListBuilder()
2502
    {
2503
        return $this->listBuilder;
2504
    }
2505
2506
    public function setShowBuilder(ShowBuilderInterface $showBuilder)
2507
    {
2508
        $this->showBuilder = $showBuilder;
2509
    }
2510
2511
    /**
2512
     * @return ShowBuilderInterface
2513
     */
2514
    public function getShowBuilder()
2515
    {
2516
        return $this->showBuilder;
2517
    }
2518
2519
    public function setConfigurationPool(Pool $configurationPool)
2520
    {
2521
        $this->configurationPool = $configurationPool;
2522
    }
2523
2524
    /**
2525
     * @return Pool
2526
     */
2527
    public function getConfigurationPool()
2528
    {
2529
        return $this->configurationPool;
2530
    }
2531
2532
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator)
2533
    {
2534
        $this->routeGenerator = $routeGenerator;
2535
    }
2536
2537
    /**
2538
     * @return RouteGeneratorInterface
2539
     */
2540
    public function getRouteGenerator()
2541
    {
2542
        return $this->routeGenerator;
2543
    }
2544
2545
    public function getCode()
2546
    {
2547
        return $this->code;
2548
    }
2549
2550
    /**
2551
     * NEXT_MAJOR: Remove this function.
2552
     *
2553
     * @deprecated This method is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0
2554
     *
2555
     * @param string $baseCodeRoute
2556
     */
2557
    public function setBaseCodeRoute($baseCodeRoute)
2558
    {
2559
        @trigger_error(
2560
            'The '.__METHOD__.' is deprecated since 3.24 and will be removed in 4.0.',
2561
            E_USER_DEPRECATED
2562
        );
2563
2564
        $this->baseCodeRoute = $baseCodeRoute;
2565
    }
2566
2567
    public function getBaseCodeRoute()
2568
    {
2569
        // NEXT_MAJOR: Uncomment the following lines.
2570
        // if ($this->isChild()) {
2571
        //     return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2572
        // }
2573
        //
2574
        // return $this->getCode();
2575
2576
        // NEXT_MAJOR: Remove all the code below.
2577
        if ($this->isChild()) {
2578
            $parentCode = $this->getParent()->getCode();
2579
2580
            if ($this->getParent()->isChild()) {
2581
                $parentCode = $this->getParent()->getBaseCodeRoute();
2582
            }
2583
2584
            return $parentCode.'|'.$this->getCode();
2585
        }
2586
2587
        return $this->baseCodeRoute;
2588
    }
2589
2590
    public function getModelManager()
2591
    {
2592
        return $this->modelManager;
2593
    }
2594
2595
    public function setModelManager(ModelManagerInterface $modelManager)
2596
    {
2597
        $this->modelManager = $modelManager;
2598
    }
2599
2600
    public function getManagerType()
2601
    {
2602
        return $this->managerType;
2603
    }
2604
2605
    /**
2606
     * @param string $type
2607
     */
2608
    public function setManagerType($type)
2609
    {
2610
        $this->managerType = $type;
2611
    }
2612
2613
    public function getObjectIdentifier()
2614
    {
2615
        return $this->getCode();
2616
    }
2617
2618
    /**
2619
     * Set the roles and permissions per role.
2620
     */
2621
    public function setSecurityInformation(array $information)
2622
    {
2623
        $this->securityInformation = $information;
2624
    }
2625
2626
    public function getSecurityInformation()
2627
    {
2628
        return $this->securityInformation;
2629
    }
2630
2631
    /**
2632
     * Return the list of permissions the user should have in order to display the admin.
2633
     *
2634
     * @param string $context
2635
     *
2636
     * @return array
2637
     */
2638
    public function getPermissionsShow($context)
2639
    {
2640
        switch ($context) {
2641
            case self::CONTEXT_DASHBOARD:
2642
            case self::CONTEXT_MENU:
2643
            default:
2644
                return ['LIST'];
2645
        }
2646
    }
2647
2648
    public function showIn($context)
2649
    {
2650
        switch ($context) {
2651
            case self::CONTEXT_DASHBOARD:
2652
            case self::CONTEXT_MENU:
2653
            default:
2654
                return $this->isGranted($this->getPermissionsShow($context));
2655
        }
2656
    }
2657
2658
    public function createObjectSecurity($object)
2659
    {
2660
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2661
    }
2662
2663
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
2664
    {
2665
        $this->securityHandler = $securityHandler;
2666
    }
2667
2668
    public function getSecurityHandler()
2669
    {
2670
        return $this->securityHandler;
2671
    }
2672
2673
    public function isGranted($name, $object = null)
2674
    {
2675
        $objectRef = $object ? '/'.spl_object_hash($object).'#'.$this->id($object) : '';
2676
        $key = md5(json_encode($name).$objectRef);
2677
2678
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
2679
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2680
        }
2681
2682
        return $this->cacheIsGranted[$key];
2683
    }
2684
2685
    public function getUrlSafeIdentifier($model)
2686
    {
2687
        return $this->getModelManager()->getUrlSafeIdentifier($model);
2688
    }
2689
2690
    public function getNormalizedIdentifier($model)
2691
    {
2692
        return $this->getModelManager()->getNormalizedIdentifier($model);
2693
    }
2694
2695
    public function id($model)
2696
    {
2697
        return $this->getNormalizedIdentifier($model);
2698
    }
2699
2700
    public function setValidator($validator)
2701
    {
2702
        // NEXT_MAJOR: Move ValidatorInterface check to method signature
2703
        if (!$validator instanceof ValidatorInterface) {
2704
            throw new \InvalidArgumentException(
2705
                'Argument 1 must be an instance of Symfony\Component\Validator\Validator\ValidatorInterface'
2706
            );
2707
        }
2708
2709
        $this->validator = $validator;
2710
    }
2711
2712
    public function getValidator()
2713
    {
2714
        return $this->validator;
2715
    }
2716
2717
    public function getShow()
2718
    {
2719
        $this->buildShow();
2720
2721
        return $this->show;
2722
    }
2723
2724
    public function setFormTheme(array $formTheme)
2725
    {
2726
        $this->formTheme = $formTheme;
2727
    }
2728
2729
    public function getFormTheme()
2730
    {
2731
        return $this->formTheme;
2732
    }
2733
2734
    public function setFilterTheme(array $filterTheme)
2735
    {
2736
        $this->filterTheme = $filterTheme;
2737
    }
2738
2739
    public function getFilterTheme()
2740
    {
2741
        return $this->filterTheme;
2742
    }
2743
2744
    public function addExtension(AdminExtensionInterface $extension)
2745
    {
2746
        $this->extensions[] = $extension;
2747
    }
2748
2749
    public function getExtensions()
2750
    {
2751
        return $this->extensions;
2752
    }
2753
2754
    public function setMenuFactory(FactoryInterface $menuFactory)
2755
    {
2756
        $this->menuFactory = $menuFactory;
2757
    }
2758
2759
    public function getMenuFactory()
2760
    {
2761
        return $this->menuFactory;
2762
    }
2763
2764
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder)
2765
    {
2766
        $this->routeBuilder = $routeBuilder;
2767
    }
2768
2769
    public function getRouteBuilder()
2770
    {
2771
        return $this->routeBuilder;
2772
    }
2773
2774
    public function toString($object)
2775
    {
2776
        if (!\is_object($object)) {
2777
            return '';
2778
        }
2779
2780
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2781
            return (string) $object;
2782
        }
2783
2784
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2785
    }
2786
2787
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy)
2788
    {
2789
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2790
    }
2791
2792
    public function getLabelTranslatorStrategy()
2793
    {
2794
        return $this->labelTranslatorStrategy;
2795
    }
2796
2797
    public function supportsPreviewMode()
2798
    {
2799
        return $this->supportsPreviewMode;
2800
    }
2801
2802
    /**
2803
     * NEXT_MAJOR: Remove this.
2804
     *
2805
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
2806
     *
2807
     * Set custom per page options.
2808
     */
2809
    public function setPerPageOptions(array $options)
2810
    {
2811
        @trigger_error(sprintf(
2812
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
2813
            __METHOD__
2814
        ), E_USER_DEPRECATED);
2815
2816
        $this->perPageOptions = $options;
2817
    }
2818
2819
    /**
2820
     * Returns predefined per page options.
2821
     *
2822
     * @return array
2823
     */
2824
    public function getPerPageOptions()
2825
    {
2826
        // NEXT_MAJOR: Remove this line and uncomment the following
2827
        return $this->perPageOptions;
2828
//        $perPageOptions = $this->getModelManager()->getDefaultPerPageOptions($this->class);
2829
//        $perPageOptions[] = $this->getMaxPerPage();
2830
//
2831
//        $perPageOptions = array_unique($perPageOptions);
2832
//        sort($perPageOptions);
2833
//
2834
//        return $perPageOptions;
2835
    }
2836
2837
    /**
2838
     * Set pager type.
2839
     *
2840
     * @param string $pagerType
2841
     */
2842
    public function setPagerType($pagerType)
2843
    {
2844
        $this->pagerType = $pagerType;
2845
    }
2846
2847
    /**
2848
     * Get pager type.
2849
     *
2850
     * @return string
2851
     */
2852
    public function getPagerType()
2853
    {
2854
        return $this->pagerType;
2855
    }
2856
2857
    /**
2858
     * Returns true if the per page value is allowed, false otherwise.
2859
     *
2860
     * @param int $perPage
2861
     *
2862
     * @return bool
2863
     */
2864
    public function determinedPerPageValue($perPage)
2865
    {
2866
        return \in_array($perPage, $this->getPerPageOptions(), true);
2867
    }
2868
2869
    public function isAclEnabled()
2870
    {
2871
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2872
    }
2873
2874
    public function getObjectMetadata($object)
2875
    {
2876
        return new Metadata($this->toString($object));
2877
    }
2878
2879
    public function getListModes()
2880
    {
2881
        return $this->listModes;
2882
    }
2883
2884
    public function setListMode($mode)
2885
    {
2886
        if (!$this->hasRequest()) {
2887
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2888
        }
2889
2890
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2891
    }
2892
2893
    public function getListMode()
2894
    {
2895
        if (!$this->hasRequest()) {
2896
            return 'list';
2897
        }
2898
2899
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2900
    }
2901
2902
    public function getAccessMapping()
2903
    {
2904
        return $this->accessMapping;
2905
    }
2906
2907
    public function checkAccess($action, $object = null)
2908
    {
2909
        $access = $this->getAccess();
2910
2911
        if (!\array_key_exists($action, $access)) {
2912
            throw new \InvalidArgumentException(sprintf(
2913
                'Action "%s" could not be found in access mapping.'
2914
                .' Please make sure your action is defined into your admin class accessMapping property.',
2915
                $action
2916
            ));
2917
        }
2918
2919
        if (!\is_array($access[$action])) {
2920
            $access[$action] = [$access[$action]];
2921
        }
2922
2923
        foreach ($access[$action] as $role) {
2924
            if (false === $this->isGranted($role, $object)) {
2925
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2926
            }
2927
        }
2928
    }
2929
2930
    /**
2931
     * Hook to handle access authorization, without throw Exception.
2932
     *
2933
     * @param string $action
2934
     * @param object $object
2935
     *
2936
     * @return bool
2937
     */
2938
    public function hasAccess($action, $object = null)
2939
    {
2940
        $access = $this->getAccess();
2941
2942
        if (!\array_key_exists($action, $access)) {
2943
            return false;
2944
        }
2945
2946
        if (!\is_array($access[$action])) {
2947
            $access[$action] = [$access[$action]];
2948
        }
2949
2950
        foreach ($access[$action] as $role) {
2951
            if (false === $this->isGranted($role, $object)) {
2952
                return false;
2953
            }
2954
        }
2955
2956
        return true;
2957
    }
2958
2959
    /**
2960
     * @param string      $action
2961
     * @param object|null $object
2962
     *
2963
     * @return array
2964
     */
2965
    public function configureActionButtons($action, $object = null)
2966
    {
2967
        $list = [];
2968
2969
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
2970
            && $this->hasAccess('create')
2971
            && $this->hasRoute('create')
2972
        ) {
2973
            $list['create'] = [
2974
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2975
                'template' => $this->getTemplate('button_create'),
2976
//                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2977
            ];
2978
        }
2979
2980
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
2981
            && $this->canAccessObject('edit', $object)
2982
            && $this->hasRoute('edit')
2983
        ) {
2984
            $list['edit'] = [
2985
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2986
                'template' => $this->getTemplate('button_edit'),
2987
                //'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2988
            ];
2989
        }
2990
2991
        if (\in_array($action, ['show', 'edit', 'acl'], true)
2992
            && $this->canAccessObject('history', $object)
2993
            && $this->hasRoute('history')
2994
        ) {
2995
            $list['history'] = [
2996
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2997
                'template' => $this->getTemplate('button_history'),
2998
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2999
            ];
3000
        }
3001
3002
        if (\in_array($action, ['edit', 'history'], true)
3003
            && $this->isAclEnabled()
3004
            && $this->canAccessObject('acl', $object)
3005
            && $this->hasRoute('acl')
3006
        ) {
3007
            $list['acl'] = [
3008
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3009
                'template' => $this->getTemplate('button_acl'),
3010
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
3011
            ];
3012
        }
3013
3014
        if (\in_array($action, ['edit', 'history', 'acl'], true)
3015
            && $this->canAccessObject('show', $object)
3016
            && \count($this->getShow()) > 0
3017
            && $this->hasRoute('show')
3018
        ) {
3019
            $list['show'] = [
3020
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3021
                'template' => $this->getTemplate('button_show'),
3022
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
3023
            ];
3024
        }
3025
3026
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
3027
            && $this->hasAccess('list')
3028
            && $this->hasRoute('list')
3029
        ) {
3030
            $list['list'] = [
3031
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3032
                'template' => $this->getTemplate('button_list'),
3033
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
3034
            ];
3035
        }
3036
3037
        return $list;
3038
    }
3039
3040
    /**
3041
     * @param string $action
3042
     * @param object $object
3043
     *
3044
     * @return array
3045
     */
3046
    public function getActionButtons($action, $object = null)
3047
    {
3048
        $list = $this->configureActionButtons($action, $object);
3049
3050
        foreach ($this->getExtensions() as $extension) {
3051
            // NEXT_MAJOR: remove method check
3052
            if (method_exists($extension, 'configureActionButtons')) {
3053
                $list = $extension->configureActionButtons($this, $list, $action, $object);
3054
            }
3055
        }
3056
3057
        return $list;
3058
    }
3059
3060
    /**
3061
     * Get the list of actions that can be accessed directly from the dashboard.
3062
     *
3063
     * @return array
3064
     */
3065
    public function getDashboardActions()
3066
    {
3067
        $actions = [];
3068
3069
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
3070
            $actions['create'] = [
3071
                'label' => 'link_add',
3072
                'translation_domain' => 'SonataAdminBundle',
3073
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3074
                'template' => $this->getTemplate('action_create'),
3075
                // 'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
3076
                'url' => $this->generateUrl('create'),
3077
                'icon' => 'plus-circle',
3078
            ];
3079
        }
3080
3081
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
3082
            $actions['list'] = [
3083
                'label' => 'link_list',
3084
                'translation_domain' => 'SonataAdminBundle',
3085
                'url' => $this->generateUrl('list'),
3086
                'icon' => 'list',
3087
            ];
3088
        }
3089
3090
        return $actions;
3091
    }
3092
3093
    /**
3094
     * Setting to true will enable mosaic button for the admin screen.
3095
     * Setting to false will hide mosaic button for the admin screen.
3096
     *
3097
     * @param bool $isShown
3098
     */
3099
    final public function showMosaicButton($isShown)
3100
    {
3101
        if ($isShown) {
3102
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
3103
        } else {
3104
            unset($this->listModes['mosaic']);
3105
        }
3106
    }
3107
3108
    /**
3109
     * @param object $object
3110
     */
3111
    final public function getSearchResultLink($object)
3112
    {
3113
        foreach ($this->searchResultActions as $action) {
3114
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
3115
                return $this->generateObjectUrl($action, $object);
3116
            }
3117
        }
3118
3119
        return null;
3120
    }
3121
3122
    /**
3123
     * Checks if a filter type is set to a default value.
3124
     *
3125
     * @param string $name
3126
     *
3127
     * @return bool
3128
     */
3129
    final public function isDefaultFilter($name)
3130
    {
3131
        $filter = $this->getFilterParameters();
3132
        $default = $this->getDefaultFilterValues();
3133
3134
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
3135
            return false;
3136
        }
3137
3138
        return $filter[$name] === $default[$name];
3139
    }
3140
3141
    /**
3142
     * Check object existence and access, without throw Exception.
3143
     *
3144
     * @param string $action
3145
     * @param object $object
3146
     *
3147
     * @return bool
3148
     */
3149
    public function canAccessObject($action, $object)
3150
    {
3151
        return $object && $this->id($object) && $this->hasAccess($action, $object);
3152
    }
3153
3154
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
3155
    {
3156
        return $query;
3157
    }
3158
3159
    /**
3160
     * @return MutableTemplateRegistryInterface
3161
     */
3162
    final protected function getTemplateRegistry()
3163
    {
3164
        return $this->templateRegistry;
3165
    }
3166
3167
    /**
3168
     * Returns a list of default sort values.
3169
     *
3170
     * @return array{_page?: int, _per_page?: int, _sort_by?: string, _sort_order?: string}
3171
     */
3172
    final protected function getDefaultSortValues(): array
3173
    {
3174
        $defaultSortValues = [];
3175
3176
        $this->configureDefaultSortValues($defaultSortValues);
3177
3178
        foreach ($this->getExtensions() as $extension) {
3179
            // NEXT_MAJOR: remove method check
3180
            if (method_exists($extension, 'configureDefaultSortValues')) {
3181
                $extension->configureDefaultSortValues($this, $defaultSortValues);
3182
            }
3183
        }
3184
3185
        return $defaultSortValues;
3186
    }
3187
3188
    /**
3189
     * Returns a list of default filters.
3190
     *
3191
     * @return array
3192
     */
3193
    final protected function getDefaultFilterValues()
3194
    {
3195
        $defaultFilterValues = [];
3196
3197
        $this->configureDefaultFilterValues($defaultFilterValues);
3198
3199
        foreach ($this->getExtensions() as $extension) {
3200
            // NEXT_MAJOR: remove method check
3201
            if (method_exists($extension, 'configureDefaultFilterValues')) {
3202
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
3203
            }
3204
        }
3205
3206
        return $defaultFilterValues;
3207
    }
3208
3209
    protected function configureFormFields(FormMapper $form)
3210
    {
3211
    }
3212
3213
    protected function configureListFields(ListMapper $list)
3214
    {
3215
    }
3216
3217
    protected function configureDatagridFilters(DatagridMapper $filter)
3218
    {
3219
    }
3220
3221
    protected function configureShowFields(ShowMapper $show)
3222
    {
3223
    }
3224
3225
    protected function configureRoutes(RouteCollection $collection)
3226
    {
3227
    }
3228
3229
    /**
3230
     * Allows you to customize batch actions.
3231
     *
3232
     * @param array $actions List of actions
3233
     *
3234
     * @return array
3235
     */
3236
    protected function configureBatchActions($actions)
3237
    {
3238
        return $actions;
3239
    }
3240
3241
    /**
3242
     * NEXT_MAJOR: remove this method.
3243
     *
3244
     * @deprecated Use configureTabMenu instead
3245
     */
3246
    protected function configureSideMenu(ItemInterface $menu, $action, ?AdminInterface $childAdmin = null)
3247
    {
3248
    }
3249
3250
    /**
3251
     * Configures the tab menu in your admin.
3252
     *
3253
     * @param string $action
3254
     */
3255
    protected function configureTabMenu(ItemInterface $menu, $action, ?AdminInterface $childAdmin = null)
3256
    {
3257
        // Use configureSideMenu not to mess with previous overrides
3258
        // NEXT_MAJOR: remove this line
3259
        $this->configureSideMenu($menu, $action, $childAdmin);
3260
    }
3261
3262
    /**
3263
     * build the view FieldDescription array.
3264
     */
3265
    protected function buildShow()
3266
    {
3267
        if ($this->loaded['show']) {
3268
            return;
3269
        }
3270
3271
        $this->loaded['show'] = true;
3272
3273
        $this->show = $this->getShowBuilder()->getBaseList();
3274
        $mapper = new ShowMapper($this->getShowBuilder(), $this->show, $this);
3275
3276
        $this->configureShowFields($mapper);
3277
3278
        foreach ($this->getExtensions() as $extension) {
3279
            $extension->configureShowFields($mapper);
3280
        }
3281
    }
3282
3283
    /**
3284
     * build the list FieldDescription array.
3285
     */
3286
    protected function buildList()
3287
    {
3288
        if ($this->loaded['list']) {
3289
            return;
3290
        }
3291
3292
        $this->loaded['list'] = true;
3293
3294
        $this->list = $this->getListBuilder()->getBaseList();
3295
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
3296
3297
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
3298
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3299
                $this->getClass(),
3300
                'batch',
3301
                [
3302
                    'label' => 'batch',
3303
                    'code' => '_batch',
3304
                    'sortable' => false,
3305
                    'virtual_field' => true,
3306
                ]
3307
            );
3308
3309
            $fieldDescription->setAdmin($this);
3310
            // NEXT_MAJOR: Remove this line and use commented line below it instead
3311
            $fieldDescription->setTemplate($this->getTemplate('batch'));
3312
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
3313
3314
            $mapper->add($fieldDescription, ListMapper::TYPE_BATCH);
3315
        }
3316
3317
        $this->configureListFields($mapper);
3318
3319
        foreach ($this->getExtensions() as $extension) {
3320
            $extension->configureListFields($mapper);
3321
        }
3322
3323
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
3324
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3325
                $this->getClass(),
3326
                'select',
3327
                [
3328
                    'label' => false,
3329
                    'code' => '_select',
3330
                    'sortable' => false,
3331
                    'virtual_field' => false,
3332
                ]
3333
            );
3334
3335
            $fieldDescription->setAdmin($this);
3336
            // NEXT_MAJOR: Remove this line and use commented line below it instead
3337
            $fieldDescription->setTemplate($this->getTemplate('select'));
3338
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
3339
3340
            $mapper->add($fieldDescription, ListMapper::TYPE_SELECT);
3341
        }
3342
    }
3343
3344
    /**
3345
     * Build the form FieldDescription collection.
3346
     */
3347
    protected function buildForm()
3348
    {
3349
        if ($this->loaded['form']) {
3350
            return;
3351
        }
3352
3353
        $this->loaded['form'] = true;
3354
3355
        // append parent object if any
3356
        // todo : clean the way the Admin class can retrieve set the object
3357
        if ($this->isChild() && $this->getParentAssociationMapping()) {
3358
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
3359
3360
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3361
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3362
3363
            $object = $this->getSubject();
3364
3365
            $value = $propertyAccessor->getValue($object, $propertyPath);
3366
3367
            if (\is_array($value) || $value instanceof \ArrayAccess) {
3368
                $value[] = $parent;
3369
                $propertyAccessor->setValue($object, $propertyPath, $value);
3370
            } else {
3371
                $propertyAccessor->setValue($object, $propertyPath, $parent);
3372
            }
3373
        }
3374
3375
        $formBuilder = $this->getFormBuilder();
3376
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
3377
            $this->preValidate($event->getData());
3378
        }, 100);
3379
3380
        $this->form = $formBuilder->getForm();
3381
    }
3382
3383
    /**
3384
     * Gets the subclass corresponding to the given name.
3385
     *
3386
     * @param string $name The name of the sub class
3387
     *
3388
     * @return string the subclass
3389
     */
3390
    protected function getSubClass($name)
3391
    {
3392
        if ($this->hasSubClass($name)) {
3393
            return $this->subClasses[$name];
3394
        }
3395
3396
        // NEXT_MAJOR: Throw \LogicException instead.
3397
        throw new \RuntimeException(sprintf(
3398
            'Unable to find the subclass `%s` for admin `%s`',
3399
            $name,
3400
            static::class
3401
        ));
3402
    }
3403
3404
    /**
3405
     * Attach the inline validator to the model metadata, this must be done once per admin.
3406
     */
3407
    protected function attachInlineValidator()
3408
    {
3409
        $admin = $this;
3410
3411
        // add the custom inline validation option
3412
        $metadata = $this->validator->getMetadataFor($this->getClass());
3413
3414
        $metadata->addConstraint(new InlineConstraint([
3415
            'service' => $this,
3416
            'method' => static function (ErrorElement $errorElement, $object) use ($admin) {
3417
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
3418
3419
                // This avoid the main validation to be cascaded to children
3420
                // The problem occurs when a model Page has a collection of Page as property
3421
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
3422
                    return;
3423
                }
3424
3425
                $admin->validate($errorElement, $object);
3426
3427
                foreach ($admin->getExtensions() as $extension) {
3428
                    $extension->validate($admin, $errorElement, $object);
3429
                }
3430
            },
3431
            'serializingWarning' => true,
3432
        ]));
3433
    }
3434
3435
    /**
3436
     * NEXT_MAJOR: Remove this function.
3437
     *
3438
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
3439
     *
3440
     * Predefine per page options.
3441
     */
3442
    protected function predefinePerPageOptions()
3443
    {
3444
        array_unshift($this->perPageOptions, $this->maxPerPage);
3445
        $this->perPageOptions = array_unique($this->perPageOptions);
3446
        sort($this->perPageOptions);
3447
    }
3448
3449
    /**
3450
     * Return list routes with permissions name.
3451
     *
3452
     * @return array<string, string>
3453
     */
3454
    protected function getAccess()
3455
    {
3456
        $access = array_merge([
3457
            'acl' => 'MASTER',
3458
            'export' => 'EXPORT',
3459
            'historyCompareRevisions' => 'EDIT',
3460
            'historyViewRevision' => 'EDIT',
3461
            'history' => 'EDIT',
3462
            'edit' => 'EDIT',
3463
            'show' => 'VIEW',
3464
            'create' => 'CREATE',
3465
            'delete' => 'DELETE',
3466
            'batchDelete' => 'DELETE',
3467
            'list' => 'LIST',
3468
        ], $this->getAccessMapping());
3469
3470
        foreach ($this->extensions as $extension) {
3471
            // NEXT_MAJOR: remove method check
3472
            if (method_exists($extension, 'getAccessMapping')) {
3473
                $access = array_merge($access, $extension->getAccessMapping($this));
3474
            }
3475
        }
3476
3477
        return $access;
3478
    }
3479
3480
    /**
3481
     * Configures a list of default filters.
3482
     */
3483
    protected function configureDefaultFilterValues(array &$filterValues)
3484
    {
3485
    }
3486
3487
    /**
3488
     * Configures a list of default sort values.
3489
     *
3490
     * Example:
3491
     *   $sortValues['_sort_by'] = 'foo'
3492
     *   $sortValues['_sort_order'] = 'DESC'
3493
     */
3494
    protected function configureDefaultSortValues(array &$sortValues)
3495
    {
3496
    }
3497
3498
    /**
3499
     * Build all the related urls to the current admin.
3500
     */
3501
    private function buildRoutes(): void
3502
    {
3503
        if ($this->loaded['routes']) {
3504
            return;
3505
        }
3506
3507
        $this->loaded['routes'] = true;
3508
3509
        $this->routes = new RouteCollection(
3510
            $this->getBaseCodeRoute(),
3511
            $this->getBaseRouteName(),
3512
            $this->getBaseRoutePattern(),
3513
            $this->getBaseControllerName()
3514
        );
3515
3516
        $this->routeBuilder->build($this, $this->routes);
3517
3518
        $this->configureRoutes($this->routes);
3519
3520
        foreach ($this->getExtensions() as $extension) {
3521
            $extension->configureRoutes($this, $this->routes);
3522
        }
3523
    }
3524
}
3525
3526
class_exists(\Sonata\Form\Validator\ErrorElement::class);
3527