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

src/Admin/AbstractAdmin.php (5 issues)

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