Completed
Push — 3.x ( 3da661...ec6eec )
by Grégoire
03:21
created

AbstractAdmin::getUrlSafeIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Admin;
15
16
use Doctrine\Common\Util\ClassUtils;
17
use Knp\Menu\FactoryInterface;
18
use Knp\Menu\ItemInterface;
19
use Sonata\AdminBundle\Builder\DatagridBuilderInterface;
20
use Sonata\AdminBundle\Builder\FormContractorInterface;
21
use Sonata\AdminBundle\Builder\ListBuilderInterface;
22
use Sonata\AdminBundle\Builder\RouteBuilderInterface;
23
use Sonata\AdminBundle\Builder\ShowBuilderInterface;
24
use Sonata\AdminBundle\Datagrid\DatagridInterface;
25
use Sonata\AdminBundle\Datagrid\DatagridMapper;
26
use Sonata\AdminBundle\Datagrid\ListMapper;
27
use Sonata\AdminBundle\Datagrid\Pager;
28
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
29
use Sonata\AdminBundle\Filter\Persister\FilterPersisterInterface;
30
use Sonata\AdminBundle\Form\FormMapper;
31
use Sonata\AdminBundle\Form\Type\ModelHiddenType;
32
use Sonata\AdminBundle\Model\ModelManagerInterface;
33
use Sonata\AdminBundle\Object\Metadata;
34
use Sonata\AdminBundle\Route\RouteCollection;
35
use Sonata\AdminBundle\Route\RouteGeneratorInterface;
36
use Sonata\AdminBundle\Security\Handler\AclSecurityHandlerInterface;
37
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
38
use Sonata\AdminBundle\Show\ShowMapper;
39
use Sonata\AdminBundle\Templating\MutableTemplateRegistryInterface;
40
use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface;
41
use Sonata\Form\Validator\Constraints\InlineConstraint;
42
use Sonata\Form\Validator\ErrorElement;
43
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
44
use Symfony\Component\Form\Form;
45
use Symfony\Component\Form\FormBuilderInterface;
46
use Symfony\Component\Form\FormEvent;
47
use Symfony\Component\Form\FormEvents;
48
use Symfony\Component\HttpFoundation\Request;
49
use Symfony\Component\PropertyAccess\PropertyPath;
50
use Symfony\Component\Routing\Generator\UrlGeneratorInterface as RoutingUrlGeneratorInterface;
51
use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
52
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
53
use Symfony\Component\Translation\TranslatorInterface;
54
use Symfony\Component\Validator\Validator\ValidatorInterface;
55
56
/**
57
 * @author Thomas Rabaix <[email protected]>
58
 */
59
abstract class AbstractAdmin implements AdminInterface, DomainObjectInterface, AdminTreeInterface
60
{
61
    public const CONTEXT_MENU = 'menu';
62
    public const CONTEXT_DASHBOARD = 'dashboard';
63
64
    public const CLASS_REGEX =
65
        '@
66
        (?:([A-Za-z0-9]*)\\\)?        # vendor name / app name
67
        (Bundle\\\)?                  # optional bundle directory
68
        ([A-Za-z0-9]+?)(?:Bundle)?\\\ # bundle name, with optional suffix
69
        (
70
            Entity|Document|Model|PHPCR|CouchDocument|Phpcr|
71
            Doctrine\\\Orm|Doctrine\\\Phpcr|Doctrine\\\MongoDB|Doctrine\\\CouchDB
72
        )\\\(.*)@x';
73
74
    public const MOSAIC_ICON_CLASS = 'fa fa-th-large fa-fw';
75
76
    /**
77
     * The list FieldDescription constructed from the configureListField method.
78
     *
79
     * @var array
80
     */
81
    protected $listFieldDescriptions = [];
82
83
    /**
84
     * The show FieldDescription constructed from the configureShowFields method.
85
     *
86
     * @var array
87
     */
88
    protected $showFieldDescriptions = [];
89
90
    /**
91
     * The list FieldDescription constructed from the configureFormField method.
92
     *
93
     * @var array
94
     */
95
    protected $formFieldDescriptions = [];
96
97
    /**
98
     * The filter FieldDescription constructed from the configureFilterField method.
99
     *
100
     * @var array
101
     */
102
    protected $filterFieldDescriptions = [];
103
104
    /**
105
     * The number of result to display in the list.
106
     *
107
     * @var int
108
     */
109
    protected $maxPerPage = 32;
110
111
    /**
112
     * The maximum number of page numbers to display in the list.
113
     *
114
     * @var int
115
     */
116
    protected $maxPageLinks = 25;
117
118
    /**
119
     * The base route name used to generate the routing information.
120
     *
121
     * @var string
122
     */
123
    protected $baseRouteName;
124
125
    /**
126
     * The base route pattern used to generate the routing information.
127
     *
128
     * @var string
129
     */
130
    protected $baseRoutePattern;
131
132
    /**
133
     * The base name controller used to generate the routing information.
134
     *
135
     * @var string
136
     */
137
    protected $baseControllerName;
138
139
    /**
140
     * The label class name  (used in the title/breadcrumb ...).
141
     *
142
     * @var string
143
     */
144
    protected $classnameLabel;
145
146
    /**
147
     * The translation domain to be used to translate messages.
148
     *
149
     * @var string
150
     */
151
    protected $translationDomain = 'messages';
152
153
    /**
154
     * Options to set to the form (ie, validation_groups).
155
     *
156
     * @var array
157
     */
158
    protected $formOptions = [];
159
160
    /**
161
     * Default values to the datagrid.
162
     *
163
     * @var array
164
     */
165
    protected $datagridValues = [
166
        '_page' => 1,
167
        '_per_page' => 32,
168
    ];
169
170
    /**
171
     * Predefined per page options.
172
     *
173
     * @var array
174
     */
175
    protected $perPageOptions = [16, 32, 64, 128, 256];
176
177
    /**
178
     * Pager type.
179
     *
180
     * @var string
181
     */
182
    protected $pagerType = Pager::TYPE_DEFAULT;
183
184
    /**
185
     * The code related to the admin.
186
     *
187
     * @var string
188
     */
189
    protected $code;
190
191
    /**
192
     * The label.
193
     *
194
     * @var string
195
     */
196
    protected $label;
197
198
    /**
199
     * Whether or not to persist the filters in the session.
200
     *
201
     * NEXT_MAJOR: remove this property
202
     *
203
     * @var bool
204
     *
205
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
206
     */
207
    protected $persistFilters = false;
208
209
    /**
210
     * Array of routes related to this admin.
211
     *
212
     * @var RouteCollection
213
     */
214
    protected $routes;
215
216
    /**
217
     * The subject only set in edit/update/create mode.
218
     *
219
     * @var object|null
220
     */
221
    protected $subject;
222
223
    /**
224
     * Define a Collection of child admin, ie /admin/order/{id}/order-element/{childId}.
225
     *
226
     * @var array
227
     */
228
    protected $children = [];
229
230
    /**
231
     * Reference the parent admin.
232
     *
233
     * @var AdminInterface|null
234
     */
235
    protected $parent;
236
237
    /**
238
     * The base code route refer to the prefix used to generate the route name.
239
     *
240
     * NEXT_MAJOR: remove this attribute.
241
     *
242
     * @deprecated This attribute is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0
243
     *
244
     * @var string
245
     */
246
    protected $baseCodeRoute = '';
247
248
    /**
249
     * NEXT_MAJOR: should be default array and private.
250
     *
251
     * @var string|array
252
     */
253
    protected $parentAssociationMapping;
254
255
    /**
256
     * Reference the parent FieldDescription related to this admin
257
     * only set for FieldDescription which is associated to an Sub Admin instance.
258
     *
259
     * @var FieldDescriptionInterface
260
     */
261
    protected $parentFieldDescription;
262
263
    /**
264
     * If true then the current admin is part of the nested admin set (from the url).
265
     *
266
     * @var bool
267
     */
268
    protected $currentChild = false;
269
270
    /**
271
     * The uniqid is used to avoid clashing with 2 admin related to the code
272
     * ie: a Block linked to a Block.
273
     *
274
     * @var string
275
     */
276
    protected $uniqid;
277
278
    /**
279
     * The Entity or Document manager.
280
     *
281
     * @var ModelManagerInterface
282
     */
283
    protected $modelManager;
284
285
    /**
286
     * The current request object.
287
     *
288
     * @var Request|null
289
     */
290
    protected $request;
291
292
    /**
293
     * The translator component.
294
     *
295
     * NEXT_MAJOR: remove this property
296
     *
297
     * @var \Symfony\Component\Translation\TranslatorInterface
298
     *
299
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
300
     */
301
    protected $translator;
302
303
    /**
304
     * The related form contractor.
305
     *
306
     * @var FormContractorInterface
307
     */
308
    protected $formContractor;
309
310
    /**
311
     * The related list builder.
312
     *
313
     * @var ListBuilderInterface
314
     */
315
    protected $listBuilder;
316
317
    /**
318
     * The related view builder.
319
     *
320
     * @var ShowBuilderInterface
321
     */
322
    protected $showBuilder;
323
324
    /**
325
     * The related datagrid builder.
326
     *
327
     * @var DatagridBuilderInterface
328
     */
329
    protected $datagridBuilder;
330
331
    /**
332
     * @var RouteBuilderInterface
333
     */
334
    protected $routeBuilder;
335
336
    /**
337
     * The datagrid instance.
338
     *
339
     * @var DatagridInterface|null
340
     */
341
    protected $datagrid;
342
343
    /**
344
     * The router instance.
345
     *
346
     * @var RouteGeneratorInterface|null
347
     */
348
    protected $routeGenerator;
349
350
    /**
351
     * The generated breadcrumbs.
352
     *
353
     * NEXT_MAJOR : remove this property
354
     *
355
     * @var array
356
     */
357
    protected $breadcrumbs = [];
358
359
    /**
360
     * @var SecurityHandlerInterface
361
     */
362
    protected $securityHandler;
363
364
    /**
365
     * @var ValidatorInterface
366
     */
367
    protected $validator;
368
369
    /**
370
     * The configuration pool.
371
     *
372
     * @var Pool
373
     */
374
    protected $configurationPool;
375
376
    /**
377
     * @var ItemInterface
378
     */
379
    protected $menu;
380
381
    /**
382
     * @var FactoryInterface
383
     */
384
    protected $menuFactory;
385
386
    /**
387
     * @var array<string, bool>
388
     */
389
    protected $loaded = [
390
        'view_fields' => false,
391
        'view_groups' => false,
392
        'routes' => false,
393
        'tab_menu' => false,
394
    ];
395
396
    /**
397
     * @var string[]
398
     */
399
    protected $formTheme = [];
400
401
    /**
402
     * @var string[]
403
     */
404
    protected $filterTheme = [];
405
406
    /**
407
     * @var array<string, string>
408
     *
409
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
410
     */
411
    protected $templates = [];
412
413
    /**
414
     * @var AdminExtensionInterface[]
415
     */
416
    protected $extensions = [];
417
418
    /**
419
     * @var LabelTranslatorStrategyInterface
420
     */
421
    protected $labelTranslatorStrategy;
422
423
    /**
424
     * Setting to true will enable preview mode for
425
     * the entity and show a preview button in the
426
     * edit/create forms.
427
     *
428
     * @var bool
429
     */
430
    protected $supportsPreviewMode = false;
431
432
    /**
433
     * Roles and permissions per role.
434
     *
435
     * @var array 'role' => ['permission', 'permission']
436
     */
437
    protected $securityInformation = [];
438
439
    protected $cacheIsGranted = [];
440
441
    /**
442
     * Action list for the search result.
443
     *
444
     * @var string[]
445
     */
446
    protected $searchResultActions = ['edit', 'show'];
447
448
    protected $listModes = [
449
        'list' => [
450
            'class' => 'fa fa-list fa-fw',
451
        ],
452
        'mosaic' => [
453
            'class' => self::MOSAIC_ICON_CLASS,
454
        ],
455
    ];
456
457
    /**
458
     * The Access mapping.
459
     *
460
     * @var array [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
461
     */
462
    protected $accessMapping = [];
463
464
    /**
465
     * @var MutableTemplateRegistryInterface
466
     */
467
    private $templateRegistry;
468
469
    /**
470
     * The class name managed by the admin class.
471
     *
472
     * @var string
473
     */
474
    private $class;
475
476
    /**
477
     * The subclasses supported by the admin class.
478
     *
479
     * @var array<string, string>
480
     */
481
    private $subClasses = [];
482
483
    /**
484
     * The list collection.
485
     *
486
     * @var FieldDescriptionCollection
487
     */
488
    private $list;
489
490
    /**
491
     * @var FieldDescriptionCollection|null
492
     */
493
    private $show;
494
495
    /**
496
     * @var Form|null
497
     */
498
    private $form;
499
500
    /**
501
     * The cached base route name.
502
     *
503
     * @var string
504
     */
505
    private $cachedBaseRouteName;
506
507
    /**
508
     * The cached base route pattern.
509
     *
510
     * @var string
511
     */
512
    private $cachedBaseRoutePattern;
513
514
    /**
515
     * The form group disposition.
516
     *
517
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
518
     * hold boolean values.
519
     *
520
     * @var array|bool
521
     */
522
    private $formGroups = false;
523
524
    /**
525
     * The form tabs disposition.
526
     *
527
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
528
     * hold boolean values.
529
     *
530
     * @var array|bool
531
     */
532
    private $formTabs = false;
533
534
    /**
535
     * The view group disposition.
536
     *
537
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
538
     * hold boolean values.
539
     *
540
     * @var array|bool
541
     */
542
    private $showGroups = false;
543
544
    /**
545
     * The view tab disposition.
546
     *
547
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
548
     * hold boolean values.
549
     *
550
     * @var array|bool
551
     */
552
    private $showTabs = false;
553
554
    /**
555
     * The manager type to use for the admin.
556
     *
557
     * @var string
558
     */
559
    private $managerType;
560
561
    /**
562
     * The breadcrumbsBuilder component.
563
     *
564
     * @var BreadcrumbsBuilderInterface
565
     */
566
    private $breadcrumbsBuilder;
567
568
    /**
569
     * Component responsible for persisting filters.
570
     *
571
     * @var FilterPersisterInterface|null
572
     */
573
    private $filterPersister;
574
575
    /**
576
     * @param string      $code
577
     * @param string      $class
578
     * @param string|null $baseControllerName
579
     */
580
    public function __construct($code, $class, $baseControllerName = null)
581
    {
582
        if (!\is_string($code)) {
583
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
584
                'Passing other type than string as argument 1 for method %s() is deprecated since sonata-project/admin-bundle 3.x. It will accept only string in version 4.0.',
585
                __METHOD__
586
            ), E_USER_DEPRECATED);
587
        }
588
        $this->code = $code;
589
        if (!\is_string($class)) {
590
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
591
                'Passing other type than string as argument 2 for method %s() is deprecated since sonata-project/admin-bundle 3.x. It will accept only string in version 4.0.',
592
                __METHOD__
593
            ), E_USER_DEPRECATED);
594
        }
595
        $this->class = $class;
596
        if (null !== $baseControllerName && !\is_string($baseControllerName)) {
597
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
598
                'Passing other type than string or null as argument 3 for method %s() is deprecated since sonata-project/admin-bundle 3.x. It will accept only string and null in version 4.0.',
599
                __METHOD__
600
            ), E_USER_DEPRECATED);
601
        }
602
        $this->baseControllerName = $baseControllerName;
603
604
        $this->predefinePerPageOptions();
605
        $this->datagridValues['_per_page'] = $this->maxPerPage;
606
    }
607
608
    /**
609
     * {@inheritdoc}
610
     */
611
    public function getExportFormats()
612
    {
613
        return [
614
            'json', 'xml', 'csv', 'xls',
615
        ];
616
    }
617
618
    /**
619
     * @return array
620
     */
621
    public function getExportFields()
622
    {
623
        $fields = $this->getModelManager()->getExportFields($this->getClass());
624
625
        foreach ($this->getExtensions() as $extension) {
626
            if (method_exists($extension, 'configureExportFields')) {
627
                $fields = $extension->configureExportFields($this, $fields);
628
            }
629
        }
630
631
        return $fields;
632
    }
633
634
    public function getDataSourceIterator()
635
    {
636
        $datagrid = $this->getDatagrid();
637
        $datagrid->buildPager();
638
639
        $fields = [];
640
641
        foreach ($this->getExportFields() as $key => $field) {
642
            $label = $this->getTranslationLabel($field, 'export', 'label');
643
            $transLabel = $this->trans($label);
644
645
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
646
            // No translation key exists
647
            if ($transLabel === $label) {
648
                $fields[$key] = $field;
649
            } else {
650
                $fields[$transLabel] = $field;
651
            }
652
        }
653
654
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
0 ignored issues
show
Bug introduced by
It seems like $datagrid defined by $this->getDatagrid() on line 636 can be null; however, Sonata\AdminBundle\Model...getDataSourceIterator() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
655
    }
656
657
    public function validate(ErrorElement $errorElement, $object)
658
    {
659
    }
660
661
    /**
662
     * define custom variable.
663
     */
664
    public function initialize()
665
    {
666
        if (!$this->classnameLabel) {
667
            /* NEXT_MAJOR: remove cast to string, null is not supposed to be
668
            supported but was documented as such */
669
            $this->classnameLabel = substr(
670
                (string) $this->getClass(),
671
                strrpos((string) $this->getClass(), '\\') + 1
672
            );
673
        }
674
675
        // NEXT_MAJOR: Remove this line.
676
        $this->baseCodeRoute = $this->getCode();
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0

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

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

Loading history...
677
678
        $this->configure();
679
    }
680
681
    public function configure()
682
    {
683
    }
684
685
    public function update($object)
686
    {
687
        $this->preUpdate($object);
688
        foreach ($this->extensions as $extension) {
689
            $extension->preUpdate($this, $object);
690
        }
691
692
        $result = $this->getModelManager()->update($object);
693
        // BC compatibility
694
        if (null !== $result) {
695
            $object = $result;
696
        }
697
698
        $this->postUpdate($object);
699
        foreach ($this->extensions as $extension) {
700
            $extension->postUpdate($this, $object);
701
        }
702
703
        return $object;
704
    }
705
706
    public function create($object)
707
    {
708
        $this->prePersist($object);
709
        foreach ($this->extensions as $extension) {
710
            $extension->prePersist($this, $object);
711
        }
712
713
        $result = $this->getModelManager()->create($object);
714
        // BC compatibility
715
        if (null !== $result) {
716
            $object = $result;
717
        }
718
719
        $this->postPersist($object);
720
        foreach ($this->extensions as $extension) {
721
            $extension->postPersist($this, $object);
722
        }
723
724
        $this->createObjectSecurity($object);
725
726
        return $object;
727
    }
728
729
    public function delete($object)
730
    {
731
        $this->preRemove($object);
732
        foreach ($this->extensions as $extension) {
733
            $extension->preRemove($this, $object);
734
        }
735
736
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
737
        $this->getModelManager()->delete($object);
738
739
        $this->postRemove($object);
740
        foreach ($this->extensions as $extension) {
741
            $extension->postRemove($this, $object);
742
        }
743
    }
744
745
    /**
746
     * @param object $object
747
     */
748
    public function preValidate($object)
0 ignored issues
show
Unused Code introduced by
The parameter $object is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
749
    {
750
    }
751
752
    public function preUpdate($object)
753
    {
754
    }
755
756
    public function postUpdate($object)
757
    {
758
    }
759
760
    public function prePersist($object)
761
    {
762
    }
763
764
    public function postPersist($object)
765
    {
766
    }
767
768
    public function preRemove($object)
769
    {
770
    }
771
772
    public function postRemove($object)
773
    {
774
    }
775
776
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
777
    {
778
    }
779
780
    public function getFilterParameters()
781
    {
782
        $parameters = [];
783
784
        // build the values array
785
        if ($this->hasRequest()) {
786
            $filters = $this->request->query->get('filter', []);
787
            if (isset($filters['_page'])) {
788
                $filters['_page'] = (int) $filters['_page'];
789
            }
790
            if (isset($filters['_per_page'])) {
791
                $filters['_per_page'] = (int) $filters['_per_page'];
792
            }
793
794
            // if filter persistence is configured
795
            // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
796
            if (false !== $this->persistFilters && null !== $this->filterPersister) {
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$persistFilters has been deprecated with message: since sonata-project/admin-bundle 3.34, to be removed in 4.0.

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

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

Loading history...
797
                // if reset filters is asked, remove from storage
798
                if ('reset' === $this->request->query->get('filters')) {
799
                    $this->filterPersister->reset($this->getCode());
800
                }
801
802
                // if no filters, fetch from storage
803
                // otherwise save to storage
804
                if (empty($filters)) {
805
                    $filters = $this->filterPersister->get($this->getCode());
806
                } else {
807
                    $this->filterPersister->set($this->getCode(), $filters);
808
                }
809
            }
810
811
            $parameters = array_merge(
812
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
813
                $this->datagridValues,
814
                $this->getDefaultFilterValues(),
815
                $filters
816
            );
817
818
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
819
                $parameters['_per_page'] = $this->maxPerPage;
820
            }
821
822
            // always force the parent value
823
            if ($this->isChild() && $this->getParentAssociationMapping()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getParentAssociationMapping() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
824
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
825
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
826
            }
827
        }
828
829
        return $parameters;
830
    }
831
832
    public function buildDatagrid()
833
    {
834
        if ($this->datagrid) {
835
            return;
836
        }
837
838
        $filterParameters = $this->getFilterParameters();
839
840
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
841
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
842
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
843
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
844
            } else {
845
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
846
                    $this->getClass(),
847
                    $filterParameters['_sort_by'],
848
                    []
849
                );
850
851
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
852
            }
853
        }
854
855
        // initialize the datagrid
856
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
857
858
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
859
860
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
861
862
        // build the datagrid filter
863
        $this->configureDatagridFilters($mapper);
864
865
        // ok, try to limit to add parent filter
866
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getParentAssociationMapping() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
867
            $mapper->add($this->getParentAssociationMapping(), null, [
868
                'show_filter' => false,
869
                'label' => false,
870
                'field_type' => ModelHiddenType::class,
871
                'field_options' => [
872
                    'model_manager' => $this->getModelManager(),
873
                ],
874
                'operator_type' => HiddenType::class,
875
            ], null, null, [
876
                'admin_code' => $this->getParent()->getCode(),
877
            ]);
878
        }
879
880
        foreach ($this->getExtensions() as $extension) {
881
            $extension->configureDatagridFilters($mapper);
882
        }
883
    }
884
885
    /**
886
     * Returns the name of the parent related field, so the field can be use to set the default
887
     * value (ie the parent object) or to filter the object.
888
     *
889
     * @throws \InvalidArgumentException
890
     *
891
     * @return string|null
892
     */
893
    public function getParentAssociationMapping()
894
    {
895
        // NEXT_MAJOR: remove array check
896
        if (\is_array($this->parentAssociationMapping) && $this->getParent()) {
897
            $parent = $this->getParent()->getCode();
898
899
            if (\array_key_exists($parent, $this->parentAssociationMapping)) {
900
                return $this->parentAssociationMapping[$parent];
901
            }
902
903
            throw new \InvalidArgumentException(sprintf(
904
                "There's no association between %s and %s.",
905
                $this->getCode(),
906
                $this->getParent()->getCode()
907
            ));
908
        }
909
910
        // NEXT_MAJOR: remove this line
911
        return $this->parentAssociationMapping;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->parentAssociationMapping; of type string|array adds the type array to the return on line 911 which is incompatible with the return type documented by Sonata\AdminBundle\Admin...arentAssociationMapping of type string|null.
Loading history...
912
    }
913
914
    /**
915
     * @param string $code
916
     * @param string $value
917
     */
918
    final public function addParentAssociationMapping($code, $value)
919
    {
920
        $this->parentAssociationMapping[$code] = $value;
921
    }
922
923
    /**
924
     * Returns the baseRoutePattern used to generate the routing information.
925
     *
926
     * @throws \RuntimeException
927
     *
928
     * @return string the baseRoutePattern used to generate the routing information
929
     */
930
    public function getBaseRoutePattern()
931
    {
932
        if (null !== $this->cachedBaseRoutePattern) {
933
            return $this->cachedBaseRoutePattern;
934
        }
935
936
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
937
            $baseRoutePattern = $this->baseRoutePattern;
938
            if (!$this->baseRoutePattern) {
939
                preg_match(self::CLASS_REGEX, $this->class, $matches);
940
941
                if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
942
                    throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', static::class));
943
                }
944
                $baseRoutePattern = $this->urlize($matches[5], '-');
945
            }
946
947
            $this->cachedBaseRoutePattern = sprintf(
948
                '%s/%s/%s',
949
                $this->getParent()->getBaseRoutePattern(),
950
                $this->getParent()->getRouterIdParameter(),
951
                $baseRoutePattern
952
            );
953
        } elseif ($this->baseRoutePattern) {
954
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
955
        } else {
956
            preg_match(self::CLASS_REGEX, $this->class, $matches);
957
958
            if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
959
                throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', static::class));
960
            }
961
962
            $this->cachedBaseRoutePattern = sprintf(
963
                '/%s%s/%s',
964
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
965
                $this->urlize($matches[3], '-'),
966
                $this->urlize($matches[5], '-')
967
            );
968
        }
969
970
        return $this->cachedBaseRoutePattern;
971
    }
972
973
    /**
974
     * Returns the baseRouteName used to generate the routing information.
975
     *
976
     * @throws \RuntimeException
977
     *
978
     * @return string the baseRouteName used to generate the routing information
979
     */
980
    public function getBaseRouteName()
981
    {
982
        if (null !== $this->cachedBaseRouteName) {
983
            return $this->cachedBaseRouteName;
984
        }
985
986
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
987
            $baseRouteName = $this->baseRouteName;
988
            if (!$this->baseRouteName) {
989
                preg_match(self::CLASS_REGEX, $this->class, $matches);
990
991
                if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
992
                    throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', static::class));
993
                }
994
                $baseRouteName = $this->urlize($matches[5]);
995
            }
996
997
            $this->cachedBaseRouteName = sprintf(
998
                '%s_%s',
999
                $this->getParent()->getBaseRouteName(),
1000
                $baseRouteName
1001
            );
1002
        } elseif ($this->baseRouteName) {
1003
            $this->cachedBaseRouteName = $this->baseRouteName;
1004
        } else {
1005
            preg_match(self::CLASS_REGEX, $this->class, $matches);
1006
1007
            if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1008
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', static::class));
1009
            }
1010
1011
            $this->cachedBaseRouteName = sprintf(
1012
                'admin_%s%s_%s',
1013
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
1014
                $this->urlize($matches[3]),
1015
                $this->urlize($matches[5])
1016
            );
1017
        }
1018
1019
        return $this->cachedBaseRouteName;
1020
    }
1021
1022
    /**
1023
     * urlize the given word.
1024
     *
1025
     * @param string $word
1026
     * @param string $sep  the separator
1027
     *
1028
     * @return string
1029
     */
1030
    public function urlize($word, $sep = '_')
1031
    {
1032
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
1033
    }
1034
1035
    public function getClass()
1036
    {
1037
        if ($this->hasActiveSubClass()) {
1038
            if ($this->getParentFieldDescription()) {
1039
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
1040
            }
1041
1042
            $subClass = $this->getRequest()->query->get('subclass');
1043
1044
            if (!$this->hasSubClass($subClass)) {
1045
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
1046
            }
1047
1048
            return $this->getSubClass($subClass);
1049
        }
1050
1051
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
1052
        if ($this->subject && \is_object($this->subject)) {
1053
            return ClassUtils::getClass($this->subject);
1054
        }
1055
1056
        return $this->class;
1057
    }
1058
1059
    public function getSubClasses()
1060
    {
1061
        return $this->subClasses;
1062
    }
1063
1064
    /**
1065
     * NEXT_MAJOR: remove this method.
1066
     */
1067
    public function addSubClass($subClass)
1068
    {
1069
        @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1070
            'Method "%s" is deprecated since sonata-project/admin-bundle 3.30 and will be removed in 4.0.',
1071
            __METHOD__
1072
        ), E_USER_DEPRECATED);
1073
1074
        if (!\in_array($subClass, $this->subClasses, true)) {
1075
            $this->subClasses[] = $subClass;
1076
        }
1077
    }
1078
1079
    public function setSubClasses(array $subClasses)
1080
    {
1081
        $this->subClasses = $subClasses;
1082
    }
1083
1084
    public function hasSubClass($name)
1085
    {
1086
        return isset($this->subClasses[$name]);
1087
    }
1088
1089
    public function hasActiveSubClass()
1090
    {
1091
        if (\count($this->subClasses) > 0 && $this->request) {
1092
            return null !== $this->getRequest()->query->get('subclass');
1093
        }
1094
1095
        return false;
1096
    }
1097
1098
    public function getActiveSubClass()
1099
    {
1100
        if (!$this->hasActiveSubClass()) {
1101
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1102
                '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. '.
1103
                'Use %s::hasActiveSubClass() to know if there is an active subclass.',
1104
                __METHOD__,
1105
                __CLASS__
1106
            ), E_USER_DEPRECATED);
1107
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1108
            // throw new \LogicException(sprintf(
1109
            //    'Admin "%s" has no active subclass.',
1110
            //    static::class
1111
            // ));
1112
1113
            return null;
1114
        }
1115
1116
        return $this->getSubClass($this->getActiveSubclassCode());
1117
    }
1118
1119
    public function getActiveSubclassCode()
1120
    {
1121
        if (!$this->hasActiveSubClass()) {
1122
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1123
                '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. '.
1124
                'Use %s::hasActiveSubClass() to know if there is an active subclass.',
1125
                __METHOD__,
1126
                __CLASS__
1127
            ), E_USER_DEPRECATED);
1128
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1129
            // throw new \LogicException(sprintf(
1130
            //    'Admin "%s" has no active subclass.',
1131
            //    static::class
1132
            // ));
1133
1134
            return null;
1135
        }
1136
1137
        $subClass = $this->getRequest()->query->get('subclass');
1138
1139
        if (!$this->hasSubClass($subClass)) {
1140
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1141
                '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. '.
1142
                'Use %s::hasActiveSubClass() to know if there is an active subclass.',
1143
                __METHOD__,
1144
                __CLASS__
1145
            ), E_USER_DEPRECATED);
1146
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1147
            // throw new \LogicException(sprintf(
1148
            //    'Admin "%s" has no active subclass.',
1149
            //    static::class
1150
            // ));
1151
1152
            return null;
1153
        }
1154
1155
        return $subClass;
1156
    }
1157
1158
    public function getBatchActions()
1159
    {
1160
        $actions = [];
1161
1162
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1163
            $actions['delete'] = [
1164
                'label' => 'action_delete',
1165
                'translation_domain' => 'SonataAdminBundle',
1166
                'ask_confirmation' => true, // by default always true
1167
            ];
1168
        }
1169
1170
        $actions = $this->configureBatchActions($actions);
1171
1172
        foreach ($this->getExtensions() as $extension) {
1173
            // NEXT_MAJOR: remove method check
1174
            if (method_exists($extension, 'configureBatchActions')) {
1175
                $actions = $extension->configureBatchActions($this, $actions);
1176
            }
1177
        }
1178
1179
        foreach ($actions  as $name => &$action) {
1180
            if (!\array_key_exists('label', $action)) {
1181
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1182
            }
1183
1184
            if (!\array_key_exists('translation_domain', $action)) {
1185
                $action['translation_domain'] = $this->getTranslationDomain();
1186
            }
1187
        }
1188
1189
        return $actions;
1190
    }
1191
1192
    public function getRoutes()
1193
    {
1194
        $this->buildRoutes();
1195
1196
        return $this->routes;
1197
    }
1198
1199
    public function getRouterIdParameter()
1200
    {
1201
        return '{'.$this->getIdParameter().'}';
1202
    }
1203
1204
    public function getIdParameter()
1205
    {
1206
        $parameter = 'id';
1207
1208
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1209
            $parameter = 'child'.ucfirst($parameter);
1210
        }
1211
1212
        return $parameter;
1213
    }
1214
1215
    public function hasRoute($name)
1216
    {
1217
        if (!$this->routeGenerator) {
1218
            throw new \RuntimeException('RouteGenerator cannot be null');
1219
        }
1220
1221
        return $this->routeGenerator->hasAdminRoute($this, $name);
1222
    }
1223
1224
    /**
1225
     * @param string      $name
1226
     * @param string|null $adminCode
1227
     *
1228
     * @return bool
1229
     */
1230
    public function isCurrentRoute($name, $adminCode = null)
1231
    {
1232
        if (!$this->hasRequest()) {
1233
            return false;
1234
        }
1235
1236
        $request = $this->getRequest();
1237
        $route = $request->get('_route');
1238
1239
        if ($adminCode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $adminCode of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1240
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1241
        } else {
1242
            $admin = $this;
1243
        }
1244
1245
        if (!$admin) {
1246
            return false;
1247
        }
1248
1249
        return ($admin->getBaseRouteName().'_'.$name) === $route;
1250
    }
1251
1252
    public function generateObjectUrl($name, $object, array $parameters = [], $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1253
    {
1254
        $parameters['id'] = $this->getUrlSafeIdentifier($object);
1255
1256
        return $this->generateUrl($name, $parameters, $referenceType);
1257
    }
1258
1259
    public function generateUrl($name, array $parameters = [], $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1260
    {
1261
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $referenceType);
1262
    }
1263
1264
    public function generateMenuUrl($name, array $parameters = [], $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1265
    {
1266
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $referenceType);
1267
    }
1268
1269
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry)
1270
    {
1271
        $this->templateRegistry = $templateRegistry;
1272
    }
1273
1274
    /**
1275
     * @param array<string, string> $templates
1276
     */
1277
    public function setTemplates(array $templates)
1278
    {
1279
        // NEXT_MAJOR: Remove this line
1280
        $this->templates = $templates;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin\AbstractAdmin::$templates has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
1281
1282
        $this->getTemplateRegistry()->setTemplates($templates);
1283
    }
1284
1285
    /**
1286
     * @param string $name
1287
     * @param string $template
1288
     */
1289
    public function setTemplate($name, $template)
1290
    {
1291
        // NEXT_MAJOR: Remove this line
1292
        $this->templates[$name] = $template;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin\AbstractAdmin::$templates has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
1293
1294
        $this->getTemplateRegistry()->setTemplate($name, $template);
1295
    }
1296
1297
    /**
1298
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1299
     *
1300
     * @return array<string, string>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1301
     */
1302
    public function getTemplates()
1303
    {
1304
        return $this->getTemplateRegistry()->getTemplates();
1305
    }
1306
1307
    /**
1308
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1309
     *
1310
     * @param string $name
1311
     *
1312
     * @return string|null
1313
     */
1314
    public function getTemplate($name)
1315
    {
1316
        return $this->getTemplateRegistry()->getTemplate($name);
1317
    }
1318
1319
    public function getNewInstance()
1320
    {
1321
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1322
        foreach ($this->getExtensions() as $extension) {
1323
            $extension->alterNewInstance($this, $object);
1324
        }
1325
1326
        return $object;
1327
    }
1328
1329
    public function getFormBuilder()
1330
    {
1331
        $this->formOptions['data_class'] = $this->getClass();
1332
1333
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1334
            $this->getUniqid(),
1335
            $this->formOptions
1336
        );
1337
1338
        $this->defineFormBuilder($formBuilder);
1339
1340
        return $formBuilder;
1341
    }
1342
1343
    /**
1344
     * This method is being called by the main admin class and the child class,
1345
     * the getFormBuilder is only call by the main admin class.
1346
     */
1347
    public function defineFormBuilder(FormBuilderInterface $formBuilder)
1348
    {
1349
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1350
1351
        $this->configureFormFields($mapper);
1352
1353
        foreach ($this->getExtensions() as $extension) {
1354
            $extension->configureFormFields($mapper);
1355
        }
1356
1357
        $this->attachInlineValidator();
1358
    }
1359
1360
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
1361
    {
1362
        $pool = $this->getConfigurationPool();
1363
1364
        $adminCode = $fieldDescription->getOption('admin_code');
1365
1366
        if (null !== $adminCode) {
1367
            $admin = $pool->getAdminByAdminCode($adminCode);
1368
        } else {
1369
            $admin = $pool->getAdminByClass($fieldDescription->getTargetEntity());
1370
        }
1371
1372
        if (!$admin) {
1373
            return;
1374
        }
1375
1376
        if ($this->hasRequest()) {
1377
            $admin->setRequest($this->getRequest());
1378
        }
1379
1380
        $fieldDescription->setAssociationAdmin($admin);
1381
    }
1382
1383
    public function getObject($id)
1384
    {
1385
        $object = $this->getModelManager()->find($this->getClass(), $id);
1386
        foreach ($this->getExtensions() as $extension) {
1387
            $extension->alterObject($this, $object);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getModelManager()...$this->getClass(), $id) on line 1385 can also be of type null; however, Sonata\AdminBundle\Admin...nterface::alterObject() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1388
        }
1389
1390
        return $object;
1391
    }
1392
1393
    public function getForm()
1394
    {
1395
        $this->buildForm();
1396
1397
        return $this->form;
1398
    }
1399
1400
    public function getList()
1401
    {
1402
        $this->buildList();
1403
1404
        return $this->list;
1405
    }
1406
1407
    /**
1408
     * @final since sonata-project/admin-bundle 3.63.0
1409
     */
1410
    public function createQuery($context = 'list')
1411
    {
1412
        if (\func_num_args() > 0) {
1413
            @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...
1414
                'The $context argument of '.__METHOD__.' is deprecated since 3.3, to be removed in 4.0.',
1415
                E_USER_DEPRECATED
1416
            );
1417
        }
1418
1419
        $query = $this->getModelManager()->createQuery($this->getClass());
1420
1421
        $query = $this->configureQuery($query);
1422
        foreach ($this->extensions as $extension) {
1423
            $extension->configureQuery($this, $query, $context);
1424
        }
1425
1426
        return $query;
1427
    }
1428
1429
    public function getDatagrid()
1430
    {
1431
        $this->buildDatagrid();
1432
1433
        return $this->datagrid;
1434
    }
1435
1436
    public function buildTabMenu($action, ?AdminInterface $childAdmin = null)
1437
    {
1438
        if ($this->loaded['tab_menu']) {
1439
            return $this->menu;
1440
        }
1441
1442
        $this->loaded['tab_menu'] = true;
1443
1444
        $menu = $this->menuFactory->createItem('root');
1445
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1446
        $menu->setExtra('translation_domain', $this->translationDomain);
1447
1448
        // Prevents BC break with KnpMenuBundle v1.x
1449
        if (method_exists($menu, 'setCurrentUri')) {
1450
            $menu->setCurrentUri($this->getRequest()->getBaseUrl().$this->getRequest()->getPathInfo());
0 ignored issues
show
Bug introduced by
The method setCurrentUri() does not exist on Knp\Menu\ItemInterface. Did you maybe mean setCurrent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1451
        }
1452
1453
        $this->configureTabMenu($menu, $action, $childAdmin);
1454
1455
        foreach ($this->getExtensions() as $extension) {
1456
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1457
        }
1458
1459
        $this->menu = $menu;
1460
1461
        return $this->menu;
1462
    }
1463
1464
    public function buildSideMenu($action, ?AdminInterface $childAdmin = null)
1465
    {
1466
        return $this->buildTabMenu($action, $childAdmin);
1467
    }
1468
1469
    /**
1470
     * @param string $action
1471
     *
1472
     * @return ItemInterface
1473
     */
1474
    public function getSideMenu($action, ?AdminInterface $childAdmin = null)
1475
    {
1476
        if ($this->isChild()) {
1477
            return $this->getParent()->getSideMenu($action, $this);
1478
        }
1479
1480
        $this->buildSideMenu($action, $childAdmin);
1481
1482
        return $this->menu;
1483
    }
1484
1485
    /**
1486
     * Returns the root code.
1487
     *
1488
     * @return string the root code
1489
     */
1490
    public function getRootCode()
1491
    {
1492
        return $this->getRoot()->getCode();
1493
    }
1494
1495
    /**
1496
     * Returns the master admin.
1497
     *
1498
     * @return AbstractAdmin the root admin class
1499
     */
1500
    public function getRoot()
1501
    {
1502
        $parentFieldDescription = $this->getParentFieldDescription();
1503
1504
        if (!$parentFieldDescription) {
1505
            return $this;
1506
        }
1507
1508
        return $parentFieldDescription->getAdmin()->getRoot();
1509
    }
1510
1511
    public function setBaseControllerName($baseControllerName)
1512
    {
1513
        $this->baseControllerName = $baseControllerName;
1514
    }
1515
1516
    public function getBaseControllerName()
1517
    {
1518
        return $this->baseControllerName;
1519
    }
1520
1521
    /**
1522
     * @param string $label
1523
     */
1524
    public function setLabel($label)
1525
    {
1526
        $this->label = $label;
1527
    }
1528
1529
    public function getLabel()
1530
    {
1531
        return $this->label;
1532
    }
1533
1534
    /**
1535
     * @param bool $persist
1536
     *
1537
     * NEXT_MAJOR: remove this method
1538
     *
1539
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
1540
     */
1541
    public function setPersistFilters($persist)
1542
    {
1543
        @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...
1544
            'The '.__METHOD__.' method is deprecated since version 3.34 and will be removed in 4.0.',
1545
            E_USER_DEPRECATED
1546
        );
1547
1548
        $this->persistFilters = $persist;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$persistFilters has been deprecated with message: since sonata-project/admin-bundle 3.34, to be removed in 4.0.

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

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

Loading history...
1549
    }
1550
1551
    public function setFilterPersister(?FilterPersisterInterface $filterPersister = null)
1552
    {
1553
        $this->filterPersister = $filterPersister;
1554
        // NEXT_MAJOR remove the deprecated property will be removed. Needed for persisted filter condition.
1555
        $this->persistFilters = true;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$persistFilters has been deprecated with message: since sonata-project/admin-bundle 3.34, to be removed in 4.0.

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

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

Loading history...
1556
    }
1557
1558
    /**
1559
     * @param int $maxPerPage
1560
     */
1561
    public function setMaxPerPage($maxPerPage)
1562
    {
1563
        $this->maxPerPage = $maxPerPage;
1564
    }
1565
1566
    /**
1567
     * @return int
1568
     */
1569
    public function getMaxPerPage()
1570
    {
1571
        return $this->maxPerPage;
1572
    }
1573
1574
    /**
1575
     * @param int $maxPageLinks
1576
     */
1577
    public function setMaxPageLinks($maxPageLinks)
1578
    {
1579
        $this->maxPageLinks = $maxPageLinks;
1580
    }
1581
1582
    /**
1583
     * @return int
1584
     */
1585
    public function getMaxPageLinks()
1586
    {
1587
        return $this->maxPageLinks;
1588
    }
1589
1590
    public function getFormGroups()
1591
    {
1592
        if (!\is_array($this->formGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1593
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1594
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.x. It will return only array in version 4.0.',
1595
                __METHOD__
1596
            ), E_USER_DEPRECATED);
1597
        }
1598
1599
        return $this->formGroups;
1600
    }
1601
1602
    public function setFormGroups(array $formGroups)
1603
    {
1604
        $this->formGroups = $formGroups;
1605
    }
1606
1607
    public function removeFieldFromFormGroup($key)
1608
    {
1609
        foreach ($this->formGroups as $name => $formGroup) {
0 ignored issues
show
Bug introduced by
The expression $this->formGroups of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1610
            unset($this->formGroups[$name]['fields'][$key]);
1611
1612
            if (empty($this->formGroups[$name]['fields'])) {
1613
                unset($this->formGroups[$name]);
1614
            }
1615
        }
1616
    }
1617
1618
    /**
1619
     * @param string $group
1620
     */
1621
    public function reorderFormGroup($group, array $keys)
1622
    {
1623
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1624
        $formGroups = $this->getFormGroups('sonata_deprecation_mute');
1625
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1626
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups('sonata_deprecation_mute') on line 1624 can also be of type boolean; however, Sonata\AdminBundle\Admin...tAdmin::setFormGroups() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1627
    }
1628
1629
    public function getFormTabs()
1630
    {
1631
        if (!\is_array($this->formTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1632
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1633
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.x. It will return only array in version 4.0.',
1634
                __METHOD__
1635
            ), E_USER_DEPRECATED);
1636
        }
1637
1638
        return $this->formTabs;
1639
    }
1640
1641
    public function setFormTabs(array $formTabs)
1642
    {
1643
        $this->formTabs = $formTabs;
1644
    }
1645
1646
    public function getShowTabs()
1647
    {
1648
        if (!\is_array($this->showTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1649
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1650
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.x. It will return only array in version 4.0.',
1651
                __METHOD__
1652
            ), E_USER_DEPRECATED);
1653
        }
1654
1655
        return $this->showTabs;
1656
    }
1657
1658
    public function setShowTabs(array $showTabs)
1659
    {
1660
        $this->showTabs = $showTabs;
1661
    }
1662
1663
    public function getShowGroups()
1664
    {
1665
        if (!\is_array($this->showGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1666
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1667
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.x. It will return only array in version 4.0.',
1668
                __METHOD__
1669
            ), E_USER_DEPRECATED);
1670
        }
1671
1672
        return $this->showGroups;
1673
    }
1674
1675
    public function setShowGroups(array $showGroups)
1676
    {
1677
        $this->showGroups = $showGroups;
1678
    }
1679
1680
    public function reorderShowGroup($group, array $keys)
1681
    {
1682
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1683
        $showGroups = $this->getShowGroups('sonata_deprecation_mute');
1684
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1685
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups('sonata_deprecation_mute') on line 1683 can also be of type boolean; however, Sonata\AdminBundle\Admin...tAdmin::setShowGroups() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1686
    }
1687
1688
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
1689
    {
1690
        $this->parentFieldDescription = $parentFieldDescription;
1691
    }
1692
1693
    public function getParentFieldDescription()
1694
    {
1695
        return $this->parentFieldDescription;
1696
    }
1697
1698
    public function hasParentFieldDescription()
1699
    {
1700
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1701
    }
1702
1703
    public function setSubject($subject)
1704
    {
1705
        if (\is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1706
            $message = <<<'EOT'
1707
You are trying to set entity an instance of "%s",
1708
which is not the one registered with this admin class ("%s").
1709
This is deprecated since 3.5 and will no longer be supported in 4.0.
1710
EOT;
1711
1712
            @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...
1713
                sprintf($message, \get_class($subject), $this->getClass()),
1714
                E_USER_DEPRECATED
1715
            ); // NEXT_MAJOR : throw an exception instead
1716
        }
1717
1718
        $this->subject = $subject;
1719
    }
1720
1721
    public function getSubject()
1722
    {
1723
        if (null === $this->subject && $this->request && !$this->hasParentFieldDescription()) {
1724
            $id = $this->request->get($this->getIdParameter());
1725
1726
            if (null !== $id) {
1727
                $this->subject = $this->getObject($id);
1728
            }
1729
        }
1730
1731
        return $this->subject;
1732
    }
1733
1734
    public function hasSubject()
1735
    {
1736
        return (bool) $this->getSubject();
1737
    }
1738
1739
    public function getFormFieldDescriptions()
1740
    {
1741
        $this->buildForm();
1742
1743
        return $this->formFieldDescriptions;
1744
    }
1745
1746
    public function getFormFieldDescription($name)
1747
    {
1748
        return $this->hasFormFieldDescription($name) ? $this->formFieldDescriptions[$name] : null;
1749
    }
1750
1751
    /**
1752
     * Returns true if the admin has a FieldDescription with the given $name.
1753
     *
1754
     * @param string $name
1755
     *
1756
     * @return bool
1757
     */
1758
    public function hasFormFieldDescription($name)
1759
    {
1760
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1761
    }
1762
1763
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1764
    {
1765
        $this->formFieldDescriptions[$name] = $fieldDescription;
1766
    }
1767
1768
    /**
1769
     * remove a FieldDescription.
1770
     *
1771
     * @param string $name
1772
     */
1773
    public function removeFormFieldDescription($name)
1774
    {
1775
        unset($this->formFieldDescriptions[$name]);
1776
    }
1777
1778
    /**
1779
     * build and return the collection of form FieldDescription.
1780
     *
1781
     * @return array collection of form FieldDescription
1782
     */
1783
    public function getShowFieldDescriptions()
1784
    {
1785
        $this->buildShow();
1786
1787
        return $this->showFieldDescriptions;
1788
    }
1789
1790
    /**
1791
     * Returns the form FieldDescription with the given $name.
1792
     *
1793
     * @param string $name
1794
     *
1795
     * @return FieldDescriptionInterface
1796
     */
1797
    public function getShowFieldDescription($name)
1798
    {
1799
        $this->buildShow();
1800
1801
        return $this->hasShowFieldDescription($name) ? $this->showFieldDescriptions[$name] : null;
1802
    }
1803
1804
    public function hasShowFieldDescription($name)
1805
    {
1806
        return \array_key_exists($name, $this->showFieldDescriptions);
1807
    }
1808
1809
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1810
    {
1811
        $this->showFieldDescriptions[$name] = $fieldDescription;
1812
    }
1813
1814
    public function removeShowFieldDescription($name)
1815
    {
1816
        unset($this->showFieldDescriptions[$name]);
1817
    }
1818
1819
    public function getListFieldDescriptions()
1820
    {
1821
        $this->buildList();
1822
1823
        return $this->listFieldDescriptions;
1824
    }
1825
1826
    public function getListFieldDescription($name)
1827
    {
1828
        return $this->hasListFieldDescription($name) ? $this->listFieldDescriptions[$name] : null;
1829
    }
1830
1831
    public function hasListFieldDescription($name)
1832
    {
1833
        $this->buildList();
1834
1835
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1836
    }
1837
1838
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1839
    {
1840
        $this->listFieldDescriptions[$name] = $fieldDescription;
1841
    }
1842
1843
    public function removeListFieldDescription($name)
1844
    {
1845
        unset($this->listFieldDescriptions[$name]);
1846
    }
1847
1848
    public function getFilterFieldDescription($name)
1849
    {
1850
        return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null;
1851
    }
1852
1853
    public function hasFilterFieldDescription($name)
1854
    {
1855
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1856
    }
1857
1858
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1859
    {
1860
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1861
    }
1862
1863
    public function removeFilterFieldDescription($name)
1864
    {
1865
        unset($this->filterFieldDescriptions[$name]);
1866
    }
1867
1868
    public function getFilterFieldDescriptions()
1869
    {
1870
        $this->buildDatagrid();
1871
1872
        return $this->filterFieldDescriptions;
1873
    }
1874
1875
    public function addChild(AdminInterface $child)
1876
    {
1877
        for ($parentAdmin = $this; null !== $parentAdmin; $parentAdmin = $parentAdmin->getParent()) {
1878
            if ($parentAdmin->getCode() !== $child->getCode()) {
1879
                continue;
1880
            }
1881
1882
            throw new \RuntimeException(sprintf(
1883
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1884
                $child->getCode(),
1885
                $this->getCode()
1886
            ));
1887
        }
1888
1889
        $this->children[$child->getCode()] = $child;
1890
1891
        $child->setParent($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Sonata\AdminBundle\Admin\AbstractAdmin>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1892
1893
        // NEXT_MAJOR: remove $args and add $field parameter to this function on next Major
1894
1895
        $args = \func_get_args();
1896
1897
        if (isset($args[1])) {
1898
            $child->addParentAssociationMapping($this->getCode(), $args[1]);
1899
        } else {
1900
            @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...
1901
                'Calling "addChild" without second argument is deprecated since'
1902
                .' sonata-project/admin-bundle 3.35 and will not be allowed in 4.0.',
1903
                E_USER_DEPRECATED
1904
            );
1905
        }
1906
    }
1907
1908
    public function hasChild($code)
1909
    {
1910
        return isset($this->children[$code]);
1911
    }
1912
1913
    public function getChildren()
1914
    {
1915
        return $this->children;
1916
    }
1917
1918
    public function getChild($code)
1919
    {
1920
        return $this->hasChild($code) ? $this->children[$code] : null;
1921
    }
1922
1923
    public function setParent(AdminInterface $parent)
1924
    {
1925
        $this->parent = $parent;
1926
    }
1927
1928
    public function getParent()
1929
    {
1930
        return $this->parent;
1931
    }
1932
1933
    final public function getRootAncestor()
1934
    {
1935
        $parent = $this;
1936
1937
        while ($parent->isChild()) {
1938
            $parent = $parent->getParent();
1939
        }
1940
1941
        return $parent;
1942
    }
1943
1944
    final public function getChildDepth()
1945
    {
1946
        $parent = $this;
1947
        $depth = 0;
1948
1949
        while ($parent->isChild()) {
1950
            $parent = $parent->getParent();
1951
            ++$depth;
1952
        }
1953
1954
        return $depth;
1955
    }
1956
1957
    final public function getCurrentLeafChildAdmin()
1958
    {
1959
        $child = $this->getCurrentChildAdmin();
1960
1961
        if (null === $child) {
1962
            return null;
1963
        }
1964
1965
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1966
            $child = $c;
1967
        }
1968
1969
        return $child;
1970
    }
1971
1972
    public function isChild()
1973
    {
1974
        return $this->parent instanceof AdminInterface;
1975
    }
1976
1977
    /**
1978
     * Returns true if the admin has children, false otherwise.
1979
     *
1980
     * @return bool if the admin has children
1981
     */
1982
    public function hasChildren()
1983
    {
1984
        return \count($this->children) > 0;
1985
    }
1986
1987
    public function setUniqid($uniqid)
1988
    {
1989
        $this->uniqid = $uniqid;
1990
    }
1991
1992
    public function getUniqid()
1993
    {
1994
        if (!$this->uniqid) {
1995
            $this->uniqid = 's'.uniqid();
1996
        }
1997
1998
        return $this->uniqid;
1999
    }
2000
2001
    /**
2002
     * Returns the classname label.
2003
     *
2004
     * @return string the classname label
2005
     */
2006
    public function getClassnameLabel()
2007
    {
2008
        return $this->classnameLabel;
2009
    }
2010
2011
    public function getPersistentParameters()
2012
    {
2013
        $parameters = [];
2014
2015
        foreach ($this->getExtensions() as $extension) {
2016
            $params = $extension->getPersistentParameters($this);
2017
2018
            if (!\is_array($params)) {
2019
                throw new \RuntimeException(sprintf('The %s::getPersistentParameters must return an array', \get_class($extension)));
2020
            }
2021
2022
            $parameters = array_merge($parameters, $params);
2023
        }
2024
2025
        return $parameters;
2026
    }
2027
2028
    /**
2029
     * @param string $name
2030
     *
2031
     * @return mixed|null
2032
     */
2033
    public function getPersistentParameter($name)
2034
    {
2035
        $parameters = $this->getPersistentParameters();
2036
2037
        return $parameters[$name] ?? null;
2038
    }
2039
2040
    public function getBreadcrumbs($action)
2041
    {
2042
        @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...
2043
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2044
            ' Use Sonata\AdminBundle\Admin\BreadcrumbsBuilder::getBreadcrumbs instead.',
2045
            E_USER_DEPRECATED
2046
        );
2047
2048
        return $this->getBreadcrumbsBuilder()->getBreadcrumbs($this, $action);
2049
    }
2050
2051
    /**
2052
     * Generates the breadcrumbs array.
2053
     *
2054
     * Note: the method will be called by the top admin instance (parent => child)
2055
     *
2056
     * @param string $action
2057
     *
2058
     * @return array
2059
     */
2060
    public function buildBreadcrumbs($action, ?ItemInterface $menu = null)
2061
    {
2062
        @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...
2063
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.',
2064
            E_USER_DEPRECATED
2065
        );
2066
2067
        if (isset($this->breadcrumbs[$action])) {
2068
            return $this->breadcrumbs[$action];
2069
        }
2070
2071
        return $this->breadcrumbs[$action] = $this->getBreadcrumbsBuilder()
2072
            ->buildBreadcrumbs($this, $action, $menu);
2073
    }
2074
2075
    /**
2076
     * NEXT_MAJOR : remove this method.
2077
     *
2078
     * @return BreadcrumbsBuilderInterface
2079
     */
2080
    final public function getBreadcrumbsBuilder()
2081
    {
2082
        @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...
2083
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2084
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
2085
            E_USER_DEPRECATED
2086
        );
2087
        if (null === $this->breadcrumbsBuilder) {
2088
            $this->breadcrumbsBuilder = new BreadcrumbsBuilder(
2089
                $this->getConfigurationPool()->getContainer()->getParameter('sonata.admin.configuration.breadcrumbs')
2090
            );
2091
        }
2092
2093
        return $this->breadcrumbsBuilder;
2094
    }
2095
2096
    /**
2097
     * NEXT_MAJOR : remove this method.
2098
     *
2099
     * @return AbstractAdmin
2100
     */
2101
    final public function setBreadcrumbsBuilder(BreadcrumbsBuilderInterface $value)
2102
    {
2103
        @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...
2104
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2105
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
2106
            E_USER_DEPRECATED
2107
        );
2108
        $this->breadcrumbsBuilder = $value;
2109
2110
        return $this;
2111
    }
2112
2113
    public function setCurrentChild($currentChild)
2114
    {
2115
        $this->currentChild = $currentChild;
2116
    }
2117
2118
    /**
2119
     * NEXT_MAJOR: Remove this method.
2120
     *
2121
     * @deprecated since sonata-project/admin-bundle 3.x, to be removed in 4.0
2122
     */
2123
    public function getCurrentChild()
2124
    {
2125
        @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...
2126
            sprintf(
2127
                'The %s() method is deprecated since version 3.x and will be removed in 4.0. Use %s::isCurrentChild() instead.',
2128
                __METHOD__,
2129
                __CLASS__
2130
            ),
2131
            E_USER_DEPRECATED
2132
        );
2133
2134
        return $this->currentChild;
2135
    }
2136
2137
    public function isCurrentChild(): bool
2138
    {
2139
        return $this->currentChild;
2140
    }
2141
2142
    /**
2143
     * Returns the current child admin instance.
2144
     *
2145
     * @return AdminInterface|null the current child admin instance
2146
     */
2147
    public function getCurrentChildAdmin()
2148
    {
2149
        foreach ($this->children as $children) {
2150
            if ($children->isCurrentChild()) {
2151
                return $children;
2152
            }
2153
        }
2154
2155
        return null;
2156
    }
2157
2158
    public function trans($id, array $parameters = [], $domain = null, $locale = null)
2159
    {
2160
        @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...
2161
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2162
            E_USER_DEPRECATED
2163
        );
2164
2165
        $domain = $domain ?: $this->getTranslationDomain();
2166
2167
        return $this->translator->trans($id, $parameters, $domain, $locale);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since sonata-project/admin-bundle 3.9, to be removed with 4.0

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

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

Loading history...
2168
    }
2169
2170
    /**
2171
     * Translate a message id.
2172
     *
2173
     * NEXT_MAJOR: remove this method
2174
     *
2175
     * @param string      $id
2176
     * @param int         $count
2177
     * @param string|null $domain
2178
     * @param string|null $locale
2179
     *
2180
     * @return string the translated string
2181
     *
2182
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2183
     */
2184
    public function transChoice($id, $count, array $parameters = [], $domain = null, $locale = null)
2185
    {
2186
        @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...
2187
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2188
            E_USER_DEPRECATED
2189
        );
2190
2191
        $domain = $domain ?: $this->getTranslationDomain();
2192
2193
        return $this->translator->transChoice($id, $count, $parameters, $domain, $locale);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since sonata-project/admin-bundle 3.9, to be removed with 4.0

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

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

Loading history...
2194
    }
2195
2196
    public function setTranslationDomain($translationDomain)
2197
    {
2198
        $this->translationDomain = $translationDomain;
2199
    }
2200
2201
    public function getTranslationDomain()
2202
    {
2203
        return $this->translationDomain;
2204
    }
2205
2206
    /**
2207
     * {@inheritdoc}
2208
     *
2209
     * NEXT_MAJOR: remove this method
2210
     *
2211
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2212
     */
2213
    public function setTranslator(TranslatorInterface $translator)
2214
    {
2215
        $args = \func_get_args();
2216
        if (isset($args[1]) && $args[1]) {
2217
            @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...
2218
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2219
                E_USER_DEPRECATED
2220
            );
2221
        }
2222
2223
        $this->translator = $translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since sonata-project/admin-bundle 3.9, to be removed with 4.0

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

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

Loading history...
2224
    }
2225
2226
    /**
2227
     * {@inheritdoc}
2228
     *
2229
     * NEXT_MAJOR: remove this method
2230
     *
2231
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2232
     */
2233
    public function getTranslator()
2234
    {
2235
        @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...
2236
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2237
            E_USER_DEPRECATED
2238
        );
2239
2240
        return $this->translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since sonata-project/admin-bundle 3.9, to be removed with 4.0

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

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

Loading history...
2241
    }
2242
2243
    public function getTranslationLabel($label, $context = '', $type = '')
2244
    {
2245
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2246
    }
2247
2248
    public function setRequest(Request $request)
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
2249
    {
2250
        $this->request = $request;
2251
2252
        foreach ($this->getChildren() as $children) {
2253
            $children->setRequest($request);
2254
        }
2255
    }
2256
2257
    public function getRequest()
2258
    {
2259
        if (!$this->request) {
2260
            throw new \RuntimeException('The Request object has not been set');
2261
        }
2262
2263
        return $this->request;
2264
    }
2265
2266
    public function hasRequest()
2267
    {
2268
        return null !== $this->request;
2269
    }
2270
2271
    public function setFormContractor(FormContractorInterface $formBuilder)
2272
    {
2273
        $this->formContractor = $formBuilder;
2274
    }
2275
2276
    /**
2277
     * @return FormContractorInterface
2278
     */
2279
    public function getFormContractor()
2280
    {
2281
        return $this->formContractor;
2282
    }
2283
2284
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder)
2285
    {
2286
        $this->datagridBuilder = $datagridBuilder;
2287
    }
2288
2289
    public function getDatagridBuilder()
2290
    {
2291
        return $this->datagridBuilder;
2292
    }
2293
2294
    public function setListBuilder(ListBuilderInterface $listBuilder)
2295
    {
2296
        $this->listBuilder = $listBuilder;
2297
    }
2298
2299
    public function getListBuilder()
2300
    {
2301
        return $this->listBuilder;
2302
    }
2303
2304
    public function setShowBuilder(ShowBuilderInterface $showBuilder)
2305
    {
2306
        $this->showBuilder = $showBuilder;
2307
    }
2308
2309
    /**
2310
     * @return ShowBuilderInterface
2311
     */
2312
    public function getShowBuilder()
2313
    {
2314
        return $this->showBuilder;
2315
    }
2316
2317
    public function setConfigurationPool(Pool $configurationPool)
2318
    {
2319
        $this->configurationPool = $configurationPool;
2320
    }
2321
2322
    /**
2323
     * @return Pool
2324
     */
2325
    public function getConfigurationPool()
2326
    {
2327
        return $this->configurationPool;
2328
    }
2329
2330
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator)
2331
    {
2332
        $this->routeGenerator = $routeGenerator;
2333
    }
2334
2335
    /**
2336
     * @return RouteGeneratorInterface
2337
     */
2338
    public function getRouteGenerator()
2339
    {
2340
        return $this->routeGenerator;
2341
    }
2342
2343
    public function getCode()
2344
    {
2345
        return $this->code;
2346
    }
2347
2348
    /**
2349
     * NEXT_MAJOR: Remove this function.
2350
     *
2351
     * @deprecated This method is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0
2352
     *
2353
     * @param string $baseCodeRoute
2354
     */
2355
    public function setBaseCodeRoute($baseCodeRoute)
2356
    {
2357
        @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...
2358
            'The '.__METHOD__.' is deprecated since 3.24 and will be removed in 4.0.',
2359
            E_USER_DEPRECATED
2360
        );
2361
2362
        $this->baseCodeRoute = $baseCodeRoute;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0

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

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

Loading history...
2363
    }
2364
2365
    public function getBaseCodeRoute()
2366
    {
2367
        // NEXT_MAJOR: Uncomment the following lines.
2368
        // if ($this->isChild()) {
2369
        //     return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2370
        // }
2371
        //
2372
        // return $this->getCode();
2373
2374
        // NEXT_MAJOR: Remove all the code below.
2375
        if ($this->isChild()) {
2376
            $parentCode = $this->getParent()->getCode();
2377
2378
            if ($this->getParent()->isChild()) {
2379
                $parentCode = $this->getParent()->getBaseCodeRoute();
2380
            }
2381
2382
            return $parentCode.'|'.$this->getCode();
2383
        }
2384
2385
        return $this->baseCodeRoute;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0

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

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

Loading history...
2386
    }
2387
2388
    public function getModelManager()
2389
    {
2390
        return $this->modelManager;
2391
    }
2392
2393
    public function setModelManager(ModelManagerInterface $modelManager)
2394
    {
2395
        $this->modelManager = $modelManager;
2396
    }
2397
2398
    public function getManagerType()
2399
    {
2400
        return $this->managerType;
2401
    }
2402
2403
    /**
2404
     * @param string $type
2405
     */
2406
    public function setManagerType($type)
2407
    {
2408
        $this->managerType = $type;
2409
    }
2410
2411
    public function getObjectIdentifier()
2412
    {
2413
        return $this->getCode();
2414
    }
2415
2416
    /**
2417
     * Set the roles and permissions per role.
2418
     */
2419
    public function setSecurityInformation(array $information)
2420
    {
2421
        $this->securityInformation = $information;
2422
    }
2423
2424
    public function getSecurityInformation()
2425
    {
2426
        return $this->securityInformation;
2427
    }
2428
2429
    /**
2430
     * Return the list of permissions the user should have in order to display the admin.
2431
     *
2432
     * @param string $context
2433
     *
2434
     * @return array
2435
     */
2436
    public function getPermissionsShow($context)
2437
    {
2438
        switch ($context) {
2439
            case self::CONTEXT_DASHBOARD:
2440
            case self::CONTEXT_MENU:
2441
            default:
2442
                return ['LIST'];
2443
        }
2444
    }
2445
2446
    public function showIn($context)
2447
    {
2448
        switch ($context) {
2449
            case self::CONTEXT_DASHBOARD:
2450
            case self::CONTEXT_MENU:
2451
            default:
2452
                return $this->isGranted($this->getPermissionsShow($context));
0 ignored issues
show
Documentation introduced by
$this->getPermissionsShow($context) is of type array<integer,string,{"0":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2453
        }
2454
    }
2455
2456
    public function createObjectSecurity($object)
2457
    {
2458
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2459
    }
2460
2461
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
2462
    {
2463
        $this->securityHandler = $securityHandler;
2464
    }
2465
2466
    public function getSecurityHandler()
2467
    {
2468
        return $this->securityHandler;
2469
    }
2470
2471
    public function isGranted($name, $object = null)
2472
    {
2473
        $objectRef = $object ? '/'.spl_object_hash($object).'#'.$this->id($object) : '';
2474
        $key = md5(json_encode($name).$objectRef);
2475
2476
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
2477
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2478
        }
2479
2480
        return $this->cacheIsGranted[$key];
2481
    }
2482
2483
    public function getUrlSafeIdentifier($entity)
2484
    {
2485
        return $this->getModelManager()->getUrlSafeIdentifier($entity);
2486
    }
2487
2488
    public function getNormalizedIdentifier($entity)
2489
    {
2490
        return $this->getModelManager()->getNormalizedIdentifier($entity);
2491
    }
2492
2493
    public function id($entity)
2494
    {
2495
        return $this->getNormalizedIdentifier($entity);
2496
    }
2497
2498
    public function setValidator($validator)
2499
    {
2500
        // NEXT_MAJOR: Move ValidatorInterface check to method signature
2501
        if (!$validator instanceof ValidatorInterface) {
2502
            throw new \InvalidArgumentException(
2503
                'Argument 1 must be an instance of Symfony\Component\Validator\Validator\ValidatorInterface'
2504
            );
2505
        }
2506
2507
        $this->validator = $validator;
2508
    }
2509
2510
    public function getValidator()
2511
    {
2512
        return $this->validator;
2513
    }
2514
2515
    public function getShow()
2516
    {
2517
        $this->buildShow();
2518
2519
        return $this->show;
2520
    }
2521
2522
    public function setFormTheme(array $formTheme)
2523
    {
2524
        $this->formTheme = $formTheme;
2525
    }
2526
2527
    public function getFormTheme()
2528
    {
2529
        return $this->formTheme;
2530
    }
2531
2532
    public function setFilterTheme(array $filterTheme)
2533
    {
2534
        $this->filterTheme = $filterTheme;
2535
    }
2536
2537
    public function getFilterTheme()
2538
    {
2539
        return $this->filterTheme;
2540
    }
2541
2542
    public function addExtension(AdminExtensionInterface $extension)
2543
    {
2544
        $this->extensions[] = $extension;
2545
    }
2546
2547
    public function getExtensions()
2548
    {
2549
        return $this->extensions;
2550
    }
2551
2552
    public function setMenuFactory(FactoryInterface $menuFactory)
2553
    {
2554
        $this->menuFactory = $menuFactory;
2555
    }
2556
2557
    public function getMenuFactory()
2558
    {
2559
        return $this->menuFactory;
2560
    }
2561
2562
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder)
2563
    {
2564
        $this->routeBuilder = $routeBuilder;
2565
    }
2566
2567
    public function getRouteBuilder()
2568
    {
2569
        return $this->routeBuilder;
2570
    }
2571
2572
    public function toString($object)
2573
    {
2574
        if (!\is_object($object)) {
2575
            return '';
2576
        }
2577
2578
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2579
            return (string) $object;
2580
        }
2581
2582
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2583
    }
2584
2585
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy)
2586
    {
2587
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2588
    }
2589
2590
    public function getLabelTranslatorStrategy()
2591
    {
2592
        return $this->labelTranslatorStrategy;
2593
    }
2594
2595
    public function supportsPreviewMode()
2596
    {
2597
        return $this->supportsPreviewMode;
2598
    }
2599
2600
    /**
2601
     * Set custom per page options.
2602
     */
2603
    public function setPerPageOptions(array $options)
2604
    {
2605
        $this->perPageOptions = $options;
2606
    }
2607
2608
    /**
2609
     * Returns predefined per page options.
2610
     *
2611
     * @return array
2612
     */
2613
    public function getPerPageOptions()
2614
    {
2615
        return $this->perPageOptions;
2616
    }
2617
2618
    /**
2619
     * Set pager type.
2620
     *
2621
     * @param string $pagerType
2622
     */
2623
    public function setPagerType($pagerType)
2624
    {
2625
        $this->pagerType = $pagerType;
2626
    }
2627
2628
    /**
2629
     * Get pager type.
2630
     *
2631
     * @return string
2632
     */
2633
    public function getPagerType()
2634
    {
2635
        return $this->pagerType;
2636
    }
2637
2638
    /**
2639
     * Returns true if the per page value is allowed, false otherwise.
2640
     *
2641
     * @param int $perPage
2642
     *
2643
     * @return bool
2644
     */
2645
    public function determinedPerPageValue($perPage)
2646
    {
2647
        return \in_array($perPage, $this->perPageOptions, true);
2648
    }
2649
2650
    public function isAclEnabled()
2651
    {
2652
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2653
    }
2654
2655
    public function getObjectMetadata($object)
2656
    {
2657
        return new Metadata($this->toString($object));
2658
    }
2659
2660
    public function getListModes()
2661
    {
2662
        return $this->listModes;
2663
    }
2664
2665
    public function setListMode($mode)
2666
    {
2667
        if (!$this->hasRequest()) {
2668
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2669
        }
2670
2671
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2672
    }
2673
2674
    public function getListMode()
2675
    {
2676
        if (!$this->hasRequest()) {
2677
            return 'list';
2678
        }
2679
2680
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2681
    }
2682
2683
    public function getAccessMapping()
2684
    {
2685
        return $this->accessMapping;
2686
    }
2687
2688
    public function checkAccess($action, $object = null)
2689
    {
2690
        $access = $this->getAccess();
2691
2692
        if (!\array_key_exists($action, $access)) {
2693
            throw new \InvalidArgumentException(sprintf(
2694
                'Action "%s" could not be found in access mapping.'
2695
                .' Please make sure your action is defined into your admin class accessMapping property.',
2696
                $action
2697
            ));
2698
        }
2699
2700
        if (!\is_array($access[$action])) {
2701
            $access[$action] = [$access[$action]];
2702
        }
2703
2704
        foreach ($access[$action] as $role) {
2705
            if (false === $this->isGranted($role, $object)) {
2706
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2707
            }
2708
        }
2709
    }
2710
2711
    /**
2712
     * Hook to handle access authorization, without throw Exception.
2713
     *
2714
     * @param string $action
2715
     * @param object $object
2716
     *
2717
     * @return bool
2718
     */
2719
    public function hasAccess($action, $object = null)
2720
    {
2721
        $access = $this->getAccess();
2722
2723
        if (!\array_key_exists($action, $access)) {
2724
            return false;
2725
        }
2726
2727
        if (!\is_array($access[$action])) {
2728
            $access[$action] = [$access[$action]];
2729
        }
2730
2731
        foreach ($access[$action] as $role) {
2732
            if (false === $this->isGranted($role, $object)) {
2733
                return false;
2734
            }
2735
        }
2736
2737
        return true;
2738
    }
2739
2740
    /**
2741
     * @param string      $action
2742
     * @param object|null $object
2743
     *
2744
     * @return array
2745
     */
2746
    public function configureActionButtons($action, $object = null)
2747
    {
2748
        $list = [];
2749
2750
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
2751
            && $this->hasAccess('create')
2752
            && $this->hasRoute('create')
2753
        ) {
2754
            $list['create'] = [
2755
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2756
                'template' => $this->getTemplate('button_create'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2757
//                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2758
            ];
2759
        }
2760
2761
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
2762
            && $this->canAccessObject('edit', $object)
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 2746 can also be of type null; however, Sonata\AdminBundle\Admin...dmin::canAccessObject() does only seem to accept object, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2763
            && $this->hasRoute('edit')
2764
        ) {
2765
            $list['edit'] = [
2766
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2767
                'template' => $this->getTemplate('button_edit'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2768
                //'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2769
            ];
2770
        }
2771
2772
        if (\in_array($action, ['show', 'edit', 'acl'], true)
2773
            && $this->canAccessObject('history', $object)
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 2746 can also be of type null; however, Sonata\AdminBundle\Admin...dmin::canAccessObject() does only seem to accept object, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2774
            && $this->hasRoute('history')
2775
        ) {
2776
            $list['history'] = [
2777
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2778
                'template' => $this->getTemplate('button_history'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2779
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2780
            ];
2781
        }
2782
2783
        if (\in_array($action, ['edit', 'history'], true)
2784
            && $this->isAclEnabled()
2785
            && $this->canAccessObject('acl', $object)
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 2746 can also be of type null; however, Sonata\AdminBundle\Admin...dmin::canAccessObject() does only seem to accept object, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2786
            && $this->hasRoute('acl')
2787
        ) {
2788
            $list['acl'] = [
2789
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2790
                'template' => $this->getTemplate('button_acl'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2791
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2792
            ];
2793
        }
2794
2795
        if (\in_array($action, ['edit', 'history', 'acl'], true)
2796
            && $this->canAccessObject('show', $object)
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 2746 can also be of type null; however, Sonata\AdminBundle\Admin...dmin::canAccessObject() does only seem to accept object, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2797
            && \count($this->getShow()) > 0
2798
            && $this->hasRoute('show')
2799
        ) {
2800
            $list['show'] = [
2801
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2802
                'template' => $this->getTemplate('button_show'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2803
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2804
            ];
2805
        }
2806
2807
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
2808
            && $this->hasAccess('list')
2809
            && $this->hasRoute('list')
2810
        ) {
2811
            $list['list'] = [
2812
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2813
                'template' => $this->getTemplate('button_list'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2814
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2815
            ];
2816
        }
2817
2818
        return $list;
2819
    }
2820
2821
    /**
2822
     * @param string $action
2823
     * @param object $object
2824
     *
2825
     * @return array
2826
     */
2827
    public function getActionButtons($action, $object = null)
2828
    {
2829
        $list = $this->configureActionButtons($action, $object);
2830
2831
        foreach ($this->getExtensions() as $extension) {
2832
            // NEXT_MAJOR: remove method check
2833
            if (method_exists($extension, 'configureActionButtons')) {
2834
                $list = $extension->configureActionButtons($this, $list, $action, $object);
2835
            }
2836
        }
2837
2838
        return $list;
2839
    }
2840
2841
    /**
2842
     * Get the list of actions that can be accessed directly from the dashboard.
2843
     *
2844
     * @return array
2845
     */
2846
    public function getDashboardActions()
2847
    {
2848
        $actions = [];
2849
2850
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2851
            $actions['create'] = [
2852
                'label' => 'link_add',
2853
                'translation_domain' => 'SonataAdminBundle',
2854
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2855
                'template' => $this->getTemplate('action_create'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2856
                // 'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2857
                'url' => $this->generateUrl('create'),
2858
                'icon' => 'plus-circle',
2859
            ];
2860
        }
2861
2862
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2863
            $actions['list'] = [
2864
                'label' => 'link_list',
2865
                'translation_domain' => 'SonataAdminBundle',
2866
                'url' => $this->generateUrl('list'),
2867
                'icon' => 'list',
2868
            ];
2869
        }
2870
2871
        return $actions;
2872
    }
2873
2874
    /**
2875
     * Setting to true will enable mosaic button for the admin screen.
2876
     * Setting to false will hide mosaic button for the admin screen.
2877
     *
2878
     * @param bool $isShown
2879
     */
2880
    final public function showMosaicButton($isShown)
2881
    {
2882
        if ($isShown) {
2883
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
2884
        } else {
2885
            unset($this->listModes['mosaic']);
2886
        }
2887
    }
2888
2889
    /**
2890
     * @param object $object
2891
     */
2892
    final public function getSearchResultLink($object)
2893
    {
2894
        foreach ($this->searchResultActions as $action) {
2895
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2896
                return $this->generateObjectUrl($action, $object);
2897
            }
2898
        }
2899
2900
        return null;
2901
    }
2902
2903
    /**
2904
     * Checks if a filter type is set to a default value.
2905
     *
2906
     * @param string $name
2907
     *
2908
     * @return bool
2909
     */
2910
    final public function isDefaultFilter($name)
2911
    {
2912
        $filter = $this->getFilterParameters();
2913
        $default = $this->getDefaultFilterValues();
2914
2915
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
2916
            return false;
2917
        }
2918
2919
        return $filter[$name] === $default[$name];
2920
    }
2921
2922
    /**
2923
     * Check object existence and access, without throw Exception.
2924
     *
2925
     * @param string $action
2926
     * @param object $object
2927
     *
2928
     * @return bool
2929
     */
2930
    public function canAccessObject($action, $object)
2931
    {
2932
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2933
    }
2934
2935
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
2936
    {
2937
        return $query;
2938
    }
2939
2940
    /**
2941
     * @return MutableTemplateRegistryInterface
2942
     */
2943
    final protected function getTemplateRegistry()
2944
    {
2945
        return $this->templateRegistry;
2946
    }
2947
2948
    /**
2949
     * Returns a list of default filters.
2950
     *
2951
     * @return array
2952
     */
2953
    final protected function getDefaultFilterValues()
2954
    {
2955
        $defaultFilterValues = [];
2956
2957
        $this->configureDefaultFilterValues($defaultFilterValues);
2958
2959
        foreach ($this->getExtensions() as $extension) {
2960
            // NEXT_MAJOR: remove method check
2961
            if (method_exists($extension, 'configureDefaultFilterValues')) {
2962
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2963
            }
2964
        }
2965
2966
        return $defaultFilterValues;
2967
    }
2968
2969
    protected function configureFormFields(FormMapper $form)
2970
    {
2971
    }
2972
2973
    protected function configureListFields(ListMapper $list)
2974
    {
2975
    }
2976
2977
    protected function configureDatagridFilters(DatagridMapper $filter)
2978
    {
2979
    }
2980
2981
    protected function configureShowFields(ShowMapper $show)
2982
    {
2983
    }
2984
2985
    protected function configureRoutes(RouteCollection $collection)
2986
    {
2987
    }
2988
2989
    /**
2990
     * Allows you to customize batch actions.
2991
     *
2992
     * @param array $actions List of actions
2993
     *
2994
     * @return array
2995
     */
2996
    protected function configureBatchActions($actions)
2997
    {
2998
        return $actions;
2999
    }
3000
3001
    /**
3002
     * NEXT_MAJOR: remove this method.
3003
     *
3004
     * @deprecated Use configureTabMenu instead
3005
     */
3006
    protected function configureSideMenu(ItemInterface $menu, $action, ?AdminInterface $childAdmin = null)
0 ignored issues
show
Unused Code introduced by
The parameter $menu is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $action is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $childAdmin is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
3007
    {
3008
    }
3009
3010
    /**
3011
     * Configures the tab menu in your admin.
3012
     *
3013
     * @param string $action
3014
     */
3015
    protected function configureTabMenu(ItemInterface $menu, $action, ?AdminInterface $childAdmin = null)
3016
    {
3017
        // Use configureSideMenu not to mess with previous overrides
3018
        // NEXT_MAJOR: remove this line
3019
        $this->configureSideMenu($menu, $action, $childAdmin);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...in::configureSideMenu() has been deprecated with message: Use configureTabMenu instead

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

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

Loading history...
3020
    }
3021
3022
    /**
3023
     * build the view FieldDescription array.
3024
     */
3025
    protected function buildShow()
3026
    {
3027
        if ($this->show) {
3028
            return;
3029
        }
3030
3031
        $this->show = new FieldDescriptionCollection();
3032
        $mapper = new ShowMapper($this->showBuilder, $this->show, $this);
3033
3034
        $this->configureShowFields($mapper);
3035
3036
        foreach ($this->getExtensions() as $extension) {
3037
            $extension->configureShowFields($mapper);
3038
        }
3039
    }
3040
3041
    /**
3042
     * build the list FieldDescription array.
3043
     */
3044
    protected function buildList()
3045
    {
3046
        if ($this->list) {
3047
            return;
3048
        }
3049
3050
        $this->list = $this->getListBuilder()->getBaseList();
3051
3052
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
3053
3054
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
3055
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3056
                $this->getClass(),
3057
                'batch',
3058
                [
3059
                    'label' => 'batch',
3060
                    'code' => '_batch',
3061
                    'sortable' => false,
3062
                    'virtual_field' => true,
3063
                ]
3064
            );
3065
3066
            $fieldDescription->setAdmin($this);
3067
            // NEXT_MAJOR: Remove this line and use commented line below it instead
3068
            $fieldDescription->setTemplate($this->getTemplate('batch'));
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
3069
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
3070
3071
            $mapper->add($fieldDescription, 'batch');
3072
        }
3073
3074
        $this->configureListFields($mapper);
3075
3076
        foreach ($this->getExtensions() as $extension) {
3077
            $extension->configureListFields($mapper);
3078
        }
3079
3080
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
3081
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3082
                $this->getClass(),
3083
                'select',
3084
                [
3085
                    'label' => false,
3086
                    'code' => '_select',
3087
                    'sortable' => false,
3088
                    'virtual_field' => false,
3089
                ]
3090
            );
3091
3092
            $fieldDescription->setAdmin($this);
3093
            // NEXT_MAJOR: Remove this line and use commented line below it instead
3094
            $fieldDescription->setTemplate($this->getTemplate('select'));
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
3095
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
3096
3097
            $mapper->add($fieldDescription, 'select');
3098
        }
3099
    }
3100
3101
    /**
3102
     * Build the form FieldDescription collection.
3103
     */
3104
    protected function buildForm()
3105
    {
3106
        if ($this->form) {
3107
            return;
3108
        }
3109
3110
        // append parent object if any
3111
        // todo : clean the way the Admin class can retrieve set the object
3112
        if ($this->isChild() && $this->getParentAssociationMapping()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getParentAssociationMapping() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
3113
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
3114
3115
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3116
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3117
3118
            $object = $this->getSubject();
3119
3120
            $value = $propertyAccessor->getValue($object, $propertyPath);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 3118 can also be of type null; however, Symfony\Component\Proper...orInterface::getValue() does only seem to accept object|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
3121
3122
            if (\is_array($value) || $value instanceof \ArrayAccess) {
3123
                $value[] = $parent;
3124
                $propertyAccessor->setValue($object, $propertyPath, $value);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 3118 can also be of type null; however, Symfony\Component\Proper...orInterface::setValue() does only seem to accept object|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
3125
            } else {
3126
                $propertyAccessor->setValue($object, $propertyPath, $parent);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 3118 can also be of type null; however, Symfony\Component\Proper...orInterface::setValue() does only seem to accept object|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
3127
            }
3128
        }
3129
3130
        $formBuilder = $this->getFormBuilder();
3131
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
3132
            $this->preValidate($event->getData());
3133
        }, 100);
3134
3135
        $this->form = $formBuilder->getForm();
0 ignored issues
show
Documentation Bug introduced by
It seems like $formBuilder->getForm() of type object<Symfony\Component\Form\FormInterface> is incompatible with the declared type object<Symfony\Component\Form\Form>|null of property $form.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
3136
    }
3137
3138
    /**
3139
     * Gets the subclass corresponding to the given name.
3140
     *
3141
     * @param string $name The name of the sub class
3142
     *
3143
     * @return string the subclass
3144
     */
3145
    protected function getSubClass($name)
3146
    {
3147
        if ($this->hasSubClass($name)) {
3148
            return $this->subClasses[$name];
3149
        }
3150
3151
        throw new \RuntimeException(sprintf(
3152
            'Unable to find the subclass `%s` for admin `%s`',
3153
            $name,
3154
            static::class
3155
        ));
3156
    }
3157
3158
    /**
3159
     * Attach the inline validator to the model metadata, this must be done once per admin.
3160
     */
3161
    protected function attachInlineValidator()
3162
    {
3163
        $admin = $this;
3164
3165
        // add the custom inline validation option
3166
        $metadata = $this->validator->getMetadataFor($this->getClass());
3167
3168
        $metadata->addConstraint(new InlineConstraint([
3169
            'service' => $this,
3170
            'method' => static function (ErrorElement $errorElement, $object) use ($admin) {
3171
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
3172
3173
                // This avoid the main validation to be cascaded to children
3174
                // The problem occurs when a model Page has a collection of Page as property
3175
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
3176
                    return;
3177
                }
3178
3179
                $admin->validate($errorElement, $object);
3180
3181
                foreach ($admin->getExtensions() as $extension) {
3182
                    $extension->validate($admin, $errorElement, $object);
3183
                }
3184
            },
3185
            'serializingWarning' => true,
3186
        ]));
3187
    }
3188
3189
    /**
3190
     * Predefine per page options.
3191
     */
3192
    protected function predefinePerPageOptions()
3193
    {
3194
        array_unshift($this->perPageOptions, $this->maxPerPage);
3195
        $this->perPageOptions = array_unique($this->perPageOptions);
3196
        sort($this->perPageOptions);
3197
    }
3198
3199
    /**
3200
     * Return list routes with permissions name.
3201
     *
3202
     * @return array<string, string>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
3203
     */
3204
    protected function getAccess()
3205
    {
3206
        $access = array_merge([
3207
            'acl' => 'MASTER',
3208
            'export' => 'EXPORT',
3209
            'historyCompareRevisions' => 'EDIT',
3210
            'historyViewRevision' => 'EDIT',
3211
            'history' => 'EDIT',
3212
            'edit' => 'EDIT',
3213
            'show' => 'VIEW',
3214
            'create' => 'CREATE',
3215
            'delete' => 'DELETE',
3216
            'batchDelete' => 'DELETE',
3217
            'list' => 'LIST',
3218
        ], $this->getAccessMapping());
3219
3220
        foreach ($this->extensions as $extension) {
3221
            // NEXT_MAJOR: remove method check
3222
            if (method_exists($extension, 'getAccessMapping')) {
3223
                $access = array_merge($access, $extension->getAccessMapping($this));
3224
            }
3225
        }
3226
3227
        return $access;
3228
    }
3229
3230
    /**
3231
     * Configures a list of default filters.
3232
     */
3233
    protected function configureDefaultFilterValues(array &$filterValues)
3234
    {
3235
    }
3236
3237
    /**
3238
     * Build all the related urls to the current admin.
3239
     */
3240
    private function buildRoutes(): void
3241
    {
3242
        if ($this->loaded['routes']) {
3243
            return;
3244
        }
3245
3246
        $this->loaded['routes'] = true;
3247
3248
        $this->routes = new RouteCollection(
3249
            $this->getBaseCodeRoute(),
3250
            $this->getBaseRouteName(),
3251
            $this->getBaseRoutePattern(),
3252
            $this->getBaseControllerName()
3253
        );
3254
3255
        $this->routeBuilder->build($this, $this->routes);
3256
3257
        $this->configureRoutes($this->routes);
3258
3259
        foreach ($this->getExtensions() as $extension) {
3260
            $extension->configureRoutes($this, $this->routes);
3261
        }
3262
    }
3263
}
3264
3265
class_exists(\Sonata\Form\Validator\ErrorElement::class);
3266