Completed
Pull Request — 3.x (#6171)
by Vincent
03:03
created

AbstractAdmin::setTemplate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

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...
627
628
        // NEXT_MAJOR: Remove this line.
629
        $this->datagridValues['_per_page'] = $this->maxPerPage;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$datagridValues has been deprecated with message: since sonata-project/admin-bundle 3.67, use configureDefaultSortValues() 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...
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$maxPerPage has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
630
    }
631
632
    /**
633
     * {@inheritdoc}
634
     */
635
    public function getExportFormats()
636
    {
637
        return [
638
            'json', 'xml', 'csv', 'xls',
639
        ];
640
    }
641
642
    /**
643
     * @return array
644
     */
645
    public function getExportFields()
646
    {
647
        $fields = $this->getModelManager()->getExportFields($this->getClass());
648
649
        foreach ($this->getExtensions() as $extension) {
650
            if (method_exists($extension, 'configureExportFields')) {
651
                $fields = $extension->configureExportFields($this, $fields);
652
            }
653
        }
654
655
        return $fields;
656
    }
657
658
    public function getDataSourceIterator()
659
    {
660
        $datagrid = $this->getDatagrid();
661
        $datagrid->buildPager();
662
663
        $fields = [];
664
665
        foreach ($this->getExportFields() as $key => $field) {
666
            $label = $this->getTranslationLabel($field, 'export', 'label');
667
            $transLabel = $this->trans($label);
668
669
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
670
            // No translation key exists
671
            if ($transLabel === $label) {
672
                $fields[$key] = $field;
673
            } else {
674
                $fields[$transLabel] = $field;
675
            }
676
        }
677
678
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
0 ignored issues
show
Bug introduced by
It seems like $datagrid defined by $this->getDatagrid() on line 660 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...
679
    }
680
681
    public function validate(ErrorElement $errorElement, $object)
682
    {
683
    }
684
685
    /**
686
     * define custom variable.
687
     */
688
    public function initialize()
689
    {
690
        if (!$this->classnameLabel) {
691
            /* NEXT_MAJOR: remove cast to string, null is not supposed to be
692
            supported but was documented as such */
693
            $this->classnameLabel = substr(
694
                (string) $this->getClass(),
695
                strrpos((string) $this->getClass(), '\\') + 1
696
            );
697
        }
698
699
        // NEXT_MAJOR: Remove this line.
700
        $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...
701
702
        $this->configure();
703
    }
704
705
    public function configure()
706
    {
707
    }
708
709
    public function update($object)
710
    {
711
        $this->preUpdate($object);
712
        foreach ($this->extensions as $extension) {
713
            $extension->preUpdate($this, $object);
714
        }
715
716
        $result = $this->getModelManager()->update($object);
717
        // BC compatibility
718
        if (null !== $result) {
719
            $object = $result;
720
        }
721
722
        $this->postUpdate($object);
723
        foreach ($this->extensions as $extension) {
724
            $extension->postUpdate($this, $object);
725
        }
726
727
        return $object;
728
    }
729
730
    public function create($object)
731
    {
732
        $this->prePersist($object);
733
        foreach ($this->extensions as $extension) {
734
            $extension->prePersist($this, $object);
735
        }
736
737
        $result = $this->getModelManager()->create($object);
738
        // BC compatibility
739
        if (null !== $result) {
740
            $object = $result;
741
        }
742
743
        $this->postPersist($object);
744
        foreach ($this->extensions as $extension) {
745
            $extension->postPersist($this, $object);
746
        }
747
748
        $this->createObjectSecurity($object);
749
750
        return $object;
751
    }
752
753
    public function delete($object)
754
    {
755
        $this->preRemove($object);
756
        foreach ($this->extensions as $extension) {
757
            $extension->preRemove($this, $object);
758
        }
759
760
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
761
        $this->getModelManager()->delete($object);
762
763
        $this->postRemove($object);
764
        foreach ($this->extensions as $extension) {
765
            $extension->postRemove($this, $object);
766
        }
767
    }
768
769
    /**
770
     * @param object $object
771
     */
772
    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...
773
    {
774
    }
775
776
    public function preUpdate($object)
777
    {
778
    }
779
780
    public function postUpdate($object)
781
    {
782
    }
783
784
    public function prePersist($object)
785
    {
786
    }
787
788
    public function postPersist($object)
789
    {
790
    }
791
792
    public function preRemove($object)
793
    {
794
    }
795
796
    public function postRemove($object)
797
    {
798
    }
799
800
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
801
    {
802
    }
803
804
    public function getFilterParameters()
805
    {
806
        $parameters = [];
807
808
        // build the values array
809
        if ($this->hasRequest()) {
810
            $filters = $this->request->query->get('filter', []);
811
            if (isset($filters['_page'])) {
812
                $filters['_page'] = (int) $filters['_page'];
813
            }
814
            if (isset($filters['_per_page'])) {
815
                $filters['_per_page'] = (int) $filters['_per_page'];
816
            }
817
818
            // if filter persistence is configured
819
            // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
820
            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...
821
                // if reset filters is asked, remove from storage
822
                if ('reset' === $this->request->query->get('filters')) {
823
                    $this->filterPersister->reset($this->getCode());
824
                }
825
826
                // if no filters, fetch from storage
827
                // otherwise save to storage
828
                if (empty($filters)) {
829
                    $filters = $this->filterPersister->get($this->getCode());
830
                } else {
831
                    $this->filterPersister->set($this->getCode(), $filters);
832
                }
833
            }
834
835
            $parameters = array_merge(
836
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
837
                $this->datagridValues, // NEXT_MAJOR: Remove this line.
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$datagridValues has been deprecated with message: since sonata-project/admin-bundle 3.67, use configureDefaultSortValues() 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...
838
                $this->getDefaultSortValues(),
839
                $this->getDefaultFilterValues(),
840
                $filters
841
            );
842
843
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
844
                $parameters['_per_page'] = $this->getMaxPerPage();
845
            }
846
847
            // always force the parent value
848
            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...
849
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
850
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
851
            }
852
        }
853
854
        return $parameters;
855
    }
856
857
    /**
858
     * NEXT_MAJOR: Change the visibility to protected (similar to buildShow, buildForm, ...).
859
     */
860
    public function buildDatagrid()
861
    {
862
        if ($this->loaded['datagrid']) {
863
            return;
864
        }
865
866
        $this->loaded['datagrid'] = true;
867
868
        $filterParameters = $this->getFilterParameters();
869
870
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
871
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
872
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
873
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
874
            } else {
875
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
876
                    $this->getClass(),
877
                    $filterParameters['_sort_by'],
878
                    []
879
                );
880
881
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
882
            }
883
        }
884
885
        // initialize the datagrid
886
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
887
888
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
889
890
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
891
892
        // build the datagrid filter
893
        $this->configureDatagridFilters($mapper);
894
895
        // ok, try to limit to add parent filter
896
        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...
897
            $mapper->add($this->getParentAssociationMapping(), null, [
898
                'show_filter' => false,
899
                'label' => false,
900
                'field_type' => ModelHiddenType::class,
901
                'field_options' => [
902
                    'model_manager' => $this->getModelManager(),
903
                ],
904
                'operator_type' => HiddenType::class,
905
            ], null, null, [
906
                'admin_code' => $this->getParent()->getCode(),
907
            ]);
908
        }
909
910
        foreach ($this->getExtensions() as $extension) {
911
            $extension->configureDatagridFilters($mapper);
912
        }
913
    }
914
915
    /**
916
     * Returns the name of the parent related field, so the field can be use to set the default
917
     * value (ie the parent object) or to filter the object.
918
     *
919
     * @throws \InvalidArgumentException
920
     *
921
     * @return string|null
922
     */
923
    public function getParentAssociationMapping()
924
    {
925
        // NEXT_MAJOR: remove array check
926
        if (\is_array($this->parentAssociationMapping) && $this->isChild()) {
927
            $parent = $this->getParent()->getCode();
928
929
            if (\array_key_exists($parent, $this->parentAssociationMapping)) {
930
                return $this->parentAssociationMapping[$parent];
931
            }
932
933
            throw new \InvalidArgumentException(sprintf(
934
                'There\'s no association between %s and %s.',
935
                $this->getCode(),
936
                $this->getParent()->getCode()
937
            ));
938
        }
939
940
        // NEXT_MAJOR: remove this line
941
        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 941 which is incompatible with the return type documented by Sonata\AdminBundle\Admin...arentAssociationMapping of type string|null.
Loading history...
942
    }
943
944
    /**
945
     * @param string $code
946
     * @param string $value
947
     */
948
    final public function addParentAssociationMapping($code, $value)
949
    {
950
        $this->parentAssociationMapping[$code] = $value;
951
    }
952
953
    /**
954
     * Returns the baseRoutePattern used to generate the routing information.
955
     *
956
     * @throws \RuntimeException
957
     *
958
     * @return string the baseRoutePattern used to generate the routing information
959
     */
960
    public function getBaseRoutePattern()
961
    {
962
        if (null !== $this->cachedBaseRoutePattern) {
963
            return $this->cachedBaseRoutePattern;
964
        }
965
966
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
967
            $baseRoutePattern = $this->baseRoutePattern;
968
            if (!$this->baseRoutePattern) {
969
                preg_match(self::CLASS_REGEX, $this->class, $matches);
970
971
                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...
972
                    throw new \RuntimeException(sprintf(
973
                        'Please define a default `baseRoutePattern` value for the admin class `%s`',
974
                        static::class
975
                    ));
976
                }
977
                $baseRoutePattern = $this->urlize($matches[5], '-');
978
            }
979
980
            $this->cachedBaseRoutePattern = sprintf(
981
                '%s/%s/%s',
982
                $this->getParent()->getBaseRoutePattern(),
983
                $this->getParent()->getRouterIdParameter(),
984
                $baseRoutePattern
985
            );
986
        } elseif ($this->baseRoutePattern) {
987
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
988
        } else {
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(
993
                    'Please define a default `baseRoutePattern` value for the admin class `%s`',
994
                    static::class
995
                ));
996
            }
997
998
            $this->cachedBaseRoutePattern = sprintf(
999
                '/%s%s/%s',
1000
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
1001
                $this->urlize($matches[3], '-'),
1002
                $this->urlize($matches[5], '-')
1003
            );
1004
        }
1005
1006
        return $this->cachedBaseRoutePattern;
1007
    }
1008
1009
    /**
1010
     * Returns the baseRouteName used to generate the routing information.
1011
     *
1012
     * @throws \RuntimeException
1013
     *
1014
     * @return string the baseRouteName used to generate the routing information
1015
     */
1016
    public function getBaseRouteName()
1017
    {
1018
        if (null !== $this->cachedBaseRouteName) {
1019
            return $this->cachedBaseRouteName;
1020
        }
1021
1022
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
1023
            $baseRouteName = $this->baseRouteName;
1024
            if (!$this->baseRouteName) {
1025
                preg_match(self::CLASS_REGEX, $this->class, $matches);
1026
1027
                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...
1028
                    throw new \RuntimeException(sprintf(
1029
                        'Cannot automatically determine base route name,'
1030
                        .' please define a default `baseRouteName` value for the admin class `%s`',
1031
                        static::class
1032
                    ));
1033
                }
1034
                $baseRouteName = $this->urlize($matches[5]);
1035
            }
1036
1037
            $this->cachedBaseRouteName = sprintf(
1038
                '%s_%s',
1039
                $this->getParent()->getBaseRouteName(),
1040
                $baseRouteName
1041
            );
1042
        } elseif ($this->baseRouteName) {
1043
            $this->cachedBaseRouteName = $this->baseRouteName;
1044
        } else {
1045
            preg_match(self::CLASS_REGEX, $this->class, $matches);
1046
1047
            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...
1048
                throw new \RuntimeException(sprintf(
1049
                    'Cannot automatically determine base route name,'
1050
                    .' please define a default `baseRouteName` value for the admin class `%s`',
1051
                    static::class
1052
                ));
1053
            }
1054
1055
            $this->cachedBaseRouteName = sprintf(
1056
                'admin_%s%s_%s',
1057
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
1058
                $this->urlize($matches[3]),
1059
                $this->urlize($matches[5])
1060
            );
1061
        }
1062
1063
        return $this->cachedBaseRouteName;
1064
    }
1065
1066
    /**
1067
     * urlize the given word.
1068
     *
1069
     * @param string $word
1070
     * @param string $sep  the separator
1071
     *
1072
     * @return string
1073
     */
1074
    public function urlize($word, $sep = '_')
1075
    {
1076
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
1077
    }
1078
1079
    public function getClass()
1080
    {
1081
        if ($this->hasActiveSubClass()) {
1082
            if ($this->hasParentFieldDescription()) {
1083
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
1084
            }
1085
1086
            $subClass = $this->getRequest()->query->get('subclass');
1087
1088
            if (!$this->hasSubClass($subClass)) {
1089
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
1090
            }
1091
1092
            return $this->getSubClass($subClass);
1093
        }
1094
1095
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
1096
        if ($this->subject && \is_object($this->subject)) {
1097
            return ClassUtils::getClass($this->subject);
1098
        }
1099
1100
        return $this->class;
1101
    }
1102
1103
    public function getSubClasses()
1104
    {
1105
        return $this->subClasses;
1106
    }
1107
1108
    /**
1109
     * NEXT_MAJOR: remove this method.
1110
     */
1111
    public function addSubClass($subClass)
1112
    {
1113
        @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...
1114
            'Method "%s" is deprecated since sonata-project/admin-bundle 3.30 and will be removed in 4.0.',
1115
            __METHOD__
1116
        ), E_USER_DEPRECATED);
1117
1118
        if (!\in_array($subClass, $this->subClasses, true)) {
1119
            $this->subClasses[] = $subClass;
1120
        }
1121
    }
1122
1123
    public function setSubClasses(array $subClasses)
1124
    {
1125
        $this->subClasses = $subClasses;
1126
    }
1127
1128
    public function hasSubClass($name)
1129
    {
1130
        return isset($this->subClasses[$name]);
1131
    }
1132
1133
    public function hasActiveSubClass()
1134
    {
1135
        if (\count($this->subClasses) > 0 && $this->request) {
1136
            return null !== $this->getRequest()->query->get('subclass');
1137
        }
1138
1139
        return false;
1140
    }
1141
1142
    public function getActiveSubClass()
1143
    {
1144
        if (!$this->hasActiveSubClass()) {
1145
            @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...
1146
                'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52'
1147
                .' and will throw an exception in 4.0.'
1148
                .' Use %s::hasActiveSubClass() to know if there is an active subclass.',
1149
                __METHOD__,
1150
                __CLASS__
1151
            ), E_USER_DEPRECATED);
1152
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1153
            // throw new \LogicException(sprintf(
1154
            //    'Admin "%s" has no active subclass.',
1155
            //    static::class
1156
            // ));
1157
1158
            return null;
1159
        }
1160
1161
        return $this->getSubClass($this->getActiveSubclassCode());
1162
    }
1163
1164
    public function getActiveSubclassCode()
1165
    {
1166
        if (!$this->hasActiveSubClass()) {
1167
            @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...
1168
                'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52'
1169
                .' and will throw an exception in 4.0.'
1170
                .' Use %s::hasActiveSubClass() to know if there is an active subclass.',
1171
                __METHOD__,
1172
                __CLASS__
1173
            ), E_USER_DEPRECATED);
1174
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1175
            // throw new \LogicException(sprintf(
1176
            //    'Admin "%s" has no active subclass.',
1177
            //    static::class
1178
            // ));
1179
1180
            return null;
1181
        }
1182
1183
        $subClass = $this->getRequest()->query->get('subclass');
1184
1185
        if (!$this->hasSubClass($subClass)) {
1186
            @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...
1187
                'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52'
1188
                .' and will throw an exception in 4.0.'
1189
                .' Use %s::hasActiveSubClass() to know if there is an active subclass.',
1190
                __METHOD__,
1191
                __CLASS__
1192
            ), E_USER_DEPRECATED);
1193
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1194
            // throw new \LogicException(sprintf(
1195
            //    'Admin "%s" has no active subclass.',
1196
            //    static::class
1197
            // ));
1198
1199
            return null;
1200
        }
1201
1202
        return $subClass;
1203
    }
1204
1205
    public function getBatchActions()
1206
    {
1207
        $actions = [];
1208
1209
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1210
            $actions['delete'] = [
1211
                'label' => 'action_delete',
1212
                'translation_domain' => 'SonataAdminBundle',
1213
                'ask_confirmation' => true, // by default always true
1214
            ];
1215
        }
1216
1217
        $actions = $this->configureBatchActions($actions);
1218
1219
        foreach ($this->getExtensions() as $extension) {
1220
            // NEXT_MAJOR: remove method check
1221
            if (method_exists($extension, 'configureBatchActions')) {
1222
                $actions = $extension->configureBatchActions($this, $actions);
1223
            }
1224
        }
1225
1226
        foreach ($actions  as $name => &$action) {
1227
            if (!\array_key_exists('label', $action)) {
1228
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1229
            }
1230
1231
            if (!\array_key_exists('translation_domain', $action)) {
1232
                $action['translation_domain'] = $this->getTranslationDomain();
1233
            }
1234
        }
1235
1236
        return $actions;
1237
    }
1238
1239
    public function getRoutes()
1240
    {
1241
        $this->buildRoutes();
1242
1243
        return $this->routes;
1244
    }
1245
1246
    public function getRouterIdParameter()
1247
    {
1248
        return sprintf('{%s}', $this->getIdParameter());
1249
    }
1250
1251
    public function getIdParameter()
1252
    {
1253
        $parameter = 'id';
1254
1255
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1256
            $parameter = sprintf('child%s', ucfirst($parameter));
1257
        }
1258
1259
        return $parameter;
1260
    }
1261
1262
    public function hasRoute($name)
1263
    {
1264
        if (!$this->routeGenerator) {
1265
            throw new \RuntimeException('RouteGenerator cannot be null');
1266
        }
1267
1268
        return $this->routeGenerator->hasAdminRoute($this, $name);
1269
    }
1270
1271
    /**
1272
     * @param string      $name
1273
     * @param string|null $adminCode
1274
     *
1275
     * @return bool
1276
     */
1277
    public function isCurrentRoute($name, $adminCode = null)
1278
    {
1279
        if (!$this->hasRequest()) {
1280
            return false;
1281
        }
1282
1283
        $request = $this->getRequest();
1284
        $route = $request->get('_route');
1285
1286
        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...
1287
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1288
        } else {
1289
            $admin = $this;
1290
        }
1291
1292
        if (!$admin) {
1293
            return false;
1294
        }
1295
1296
        return sprintf('%s_%s', $admin->getBaseRouteName(), $name) === $route;
1297
    }
1298
1299
    public function generateObjectUrl($name, $object, array $parameters = [], $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1300
    {
1301
        $parameters['id'] = $this->getUrlSafeIdentifier($object);
1302
1303
        return $this->generateUrl($name, $parameters, $referenceType);
1304
    }
1305
1306
    public function generateUrl($name, array $parameters = [], $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1307
    {
1308
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $referenceType);
1309
    }
1310
1311
    public function generateMenuUrl($name, array $parameters = [], $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1312
    {
1313
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $referenceType);
1314
    }
1315
1316
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry)
1317
    {
1318
        $this->templateRegistry = $templateRegistry;
1319
    }
1320
1321
    /**
1322
     * @param array<string, string> $templates
1323
     */
1324
    public function setTemplates(array $templates)
1325
    {
1326
        // NEXT_MAJOR: Remove this line
1327
        $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...
1328
1329
        $this->getTemplateRegistry()->setTemplates($templates);
1330
    }
1331
1332
    /**
1333
     * @param string $name
1334
     * @param string $template
1335
     */
1336
    public function setTemplate($name, $template)
1337
    {
1338
        // NEXT_MAJOR: Remove this line
1339
        $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...
1340
1341
        $this->getTemplateRegistry()->setTemplate($name, $template);
1342
    }
1343
1344
    /**
1345
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1346
     *
1347
     * @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...
1348
     */
1349
    public function getTemplates()
1350
    {
1351
        return $this->getTemplateRegistry()->getTemplates();
1352
    }
1353
1354
    /**
1355
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1356
     *
1357
     * @param string $name
1358
     *
1359
     * @return string|null
1360
     */
1361
    public function getTemplate($name)
1362
    {
1363
        return $this->getTemplateRegistry()->getTemplate($name);
1364
    }
1365
1366
    public function getNewInstance()
1367
    {
1368
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1369
1370
        $this->appendParentObject($object);
1371
1372
        foreach ($this->getExtensions() as $extension) {
1373
            $extension->alterNewInstance($this, $object);
1374
        }
1375
1376
        return $object;
1377
    }
1378
1379
    public function getFormBuilder()
1380
    {
1381
        $this->formOptions['data_class'] = $this->getClass();
1382
1383
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1384
            $this->getUniqid(),
1385
            $this->formOptions
1386
        );
1387
1388
        $this->defineFormBuilder($formBuilder);
1389
1390
        return $formBuilder;
1391
    }
1392
1393
    /**
1394
     * This method is being called by the main admin class and the child class,
1395
     * the getFormBuilder is only call by the main admin class.
1396
     */
1397
    public function defineFormBuilder(FormBuilderInterface $formBuilder)
1398
    {
1399
        if (!$this->hasSubject()) {
1400
            @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...
1401
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.65'
1402
                .' and will throw an exception in 4.0. Use %s::setSubject() to set the subject.',
1403
                __METHOD__,
1404
                __CLASS__
1405
            ), E_USER_DEPRECATED);
1406
            // NEXT_MAJOR : remove the previous `trigger_error()` call and uncomment the following exception
1407
            // throw new \LogicException(sprintf(
1408
            //    'Admin "%s" has no subject.',
1409
            //    static::class
1410
            // ));
1411
        }
1412
1413
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1414
1415
        $this->configureFormFields($mapper);
1416
1417
        foreach ($this->getExtensions() as $extension) {
1418
            $extension->configureFormFields($mapper);
1419
        }
1420
1421
        $this->attachInlineValidator();
1422
    }
1423
1424
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
1425
    {
1426
        $pool = $this->getConfigurationPool();
1427
1428
        $adminCode = $fieldDescription->getOption('admin_code');
1429
1430
        if (null !== $adminCode) {
1431
            if (!$pool->hasAdminByAdminCode($adminCode)) {
1432
                return;
1433
            }
1434
1435
            $admin = $pool->getAdminByAdminCode($adminCode);
1436
        } else {
1437
            if (!$pool->hasAdminByClass($fieldDescription->getTargetModel())) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sonata\AdminBundle\Admin\FieldDescriptionInterface as the method getTargetModel() does only exist in the following implementations of said interface: Sonata\AdminBundle\Tests...\Admin\FieldDescription, Sonata\AdminBundle\Tests...\Admin\FieldDescription.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1438
                return;
1439
            }
1440
1441
            $admin = $pool->getAdminByClass($fieldDescription->getTargetModel());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sonata\AdminBundle\Admin\FieldDescriptionInterface as the method getTargetModel() does only exist in the following implementations of said interface: Sonata\AdminBundle\Tests...\Admin\FieldDescription, Sonata\AdminBundle\Tests...\Admin\FieldDescription.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1442
        }
1443
1444
        if ($this->hasRequest()) {
1445
            $admin->setRequest($this->getRequest());
1446
        }
1447
1448
        $fieldDescription->setAssociationAdmin($admin);
0 ignored issues
show
Bug introduced by
It seems like $admin can also be of type false or null; however, Sonata\AdminBundle\Admin...::setAssociationAdmin() does only seem to accept object<Sonata\AdminBundle\Admin\AdminInterface>, 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...
1449
    }
1450
1451
    public function getObject($id)
1452
    {
1453
        $object = $this->getModelManager()->find($this->getClass(), $id);
1454
        foreach ($this->getExtensions() as $extension) {
1455
            $extension->alterObject($this, $object);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getModelManager()...$this->getClass(), $id) on line 1453 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...
1456
        }
1457
1458
        return $object;
1459
    }
1460
1461
    public function getForm()
1462
    {
1463
        $this->buildForm();
1464
1465
        return $this->form;
1466
    }
1467
1468
    public function getList()
1469
    {
1470
        $this->buildList();
1471
1472
        return $this->list;
1473
    }
1474
1475
    /**
1476
     * @final since sonata-project/admin-bundle 3.63.0
1477
     */
1478
    public function createQuery($context = 'list')
1479
    {
1480
        if (\func_num_args() > 0) {
1481
            @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...
1482
                'The $context argument of %s is deprecated since 3.3, to be removed in 4.0.',
1483
                __METHOD__
1484
            ), E_USER_DEPRECATED);
1485
        }
1486
1487
        $query = $this->getModelManager()->createQuery($this->getClass());
1488
1489
        $query = $this->configureQuery($query);
1490
        foreach ($this->extensions as $extension) {
1491
            $extension->configureQuery($this, $query, $context);
1492
        }
1493
1494
        return $query;
1495
    }
1496
1497
    public function getDatagrid()
1498
    {
1499
        $this->buildDatagrid();
1500
1501
        return $this->datagrid;
1502
    }
1503
1504
    public function buildTabMenu($action, ?AdminInterface $childAdmin = null)
1505
    {
1506
        if ($this->loaded['tab_menu']) {
1507
            return $this->menu;
1508
        }
1509
1510
        $this->loaded['tab_menu'] = true;
1511
1512
        $menu = $this->menuFactory->createItem('root');
1513
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1514
        $menu->setExtra('translation_domain', $this->translationDomain);
1515
1516
        // Prevents BC break with KnpMenuBundle v1.x
1517
        if (method_exists($menu, 'setCurrentUri')) {
1518
            $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...
1519
        }
1520
1521
        $this->configureTabMenu($menu, $action, $childAdmin);
1522
1523
        foreach ($this->getExtensions() as $extension) {
1524
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1525
        }
1526
1527
        $this->menu = $menu;
1528
1529
        return $this->menu;
1530
    }
1531
1532
    public function buildSideMenu($action, ?AdminInterface $childAdmin = null)
1533
    {
1534
        return $this->buildTabMenu($action, $childAdmin);
1535
    }
1536
1537
    /**
1538
     * @param string $action
1539
     *
1540
     * @return ItemInterface
1541
     */
1542
    public function getSideMenu($action, ?AdminInterface $childAdmin = null)
1543
    {
1544
        if ($this->isChild()) {
1545
            return $this->getParent()->getSideMenu($action, $this);
1546
        }
1547
1548
        $this->buildSideMenu($action, $childAdmin);
1549
1550
        return $this->menu;
1551
    }
1552
1553
    /**
1554
     * Returns the root code.
1555
     *
1556
     * @return string the root code
1557
     */
1558
    public function getRootCode()
1559
    {
1560
        return $this->getRoot()->getCode();
1561
    }
1562
1563
    /**
1564
     * Returns the master admin.
1565
     *
1566
     * @return AbstractAdmin the root admin class
1567
     */
1568
    public function getRoot()
1569
    {
1570
        if (!$this->hasParentFieldDescription()) {
1571
            return $this;
1572
        }
1573
1574
        return $this->getParentFieldDescription()->getAdmin()->getRoot();
1575
    }
1576
1577
    public function setBaseControllerName($baseControllerName)
1578
    {
1579
        $this->baseControllerName = $baseControllerName;
1580
    }
1581
1582
    public function getBaseControllerName()
1583
    {
1584
        return $this->baseControllerName;
1585
    }
1586
1587
    /**
1588
     * @param string $label
1589
     */
1590
    public function setLabel($label)
1591
    {
1592
        $this->label = $label;
1593
    }
1594
1595
    public function getLabel()
1596
    {
1597
        return $this->label;
1598
    }
1599
1600
    /**
1601
     * @param bool $persist
1602
     *
1603
     * NEXT_MAJOR: remove this method
1604
     *
1605
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
1606
     */
1607
    public function setPersistFilters($persist)
1608
    {
1609
        @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...
1610
            'The %s method is deprecated since version 3.34 and will be removed in 4.0.',
1611
            __METHOD__
1612
        ), E_USER_DEPRECATED);
1613
1614
        $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...
1615
    }
1616
1617
    public function setFilterPersister(?FilterPersisterInterface $filterPersister = null)
1618
    {
1619
        $this->filterPersister = $filterPersister;
1620
        // NEXT_MAJOR remove the deprecated property will be removed. Needed for persisted filter condition.
1621
        $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...
1622
    }
1623
1624
    /**
1625
     * NEXT_MAJOR: Remove this method.
1626
     *
1627
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
1628
     *
1629
     * @param int $maxPerPage
1630
     */
1631
    public function setMaxPerPage($maxPerPage)
1632
    {
1633
        @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...
1634
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
1635
            __METHOD__
1636
        ), E_USER_DEPRECATED);
1637
1638
        $this->maxPerPage = $maxPerPage;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$maxPerPage has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
1639
    }
1640
1641
    /**
1642
     * @return int
1643
     */
1644
    public function getMaxPerPage()
1645
    {
1646
        // NEXT_MAJOR: Remove this line and uncomment the following.
1647
        return $this->maxPerPage;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$maxPerPage has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
1648
        // $sortValues = $this->getModelManager()->getDefaultSortValues($this->class);
1649
1650
        // return $sortValues['_per_page'] ?? 25;
1651
    }
1652
1653
    /**
1654
     * @param int $maxPageLinks
1655
     */
1656
    public function setMaxPageLinks($maxPageLinks)
1657
    {
1658
        $this->maxPageLinks = $maxPageLinks;
1659
    }
1660
1661
    /**
1662
     * @return int
1663
     */
1664
    public function getMaxPageLinks()
1665
    {
1666
        return $this->maxPageLinks;
1667
    }
1668
1669
    public function getFormGroups()
1670
    {
1671
        if (!\is_array($this->formGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1672
            @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...
1673
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65.'
1674
                .' It will return only array in version 4.0.',
1675
                __METHOD__
1676
            ), E_USER_DEPRECATED);
1677
        }
1678
1679
        return $this->formGroups;
1680
    }
1681
1682
    public function setFormGroups(array $formGroups)
1683
    {
1684
        $this->formGroups = $formGroups;
1685
    }
1686
1687
    public function removeFieldFromFormGroup($key)
1688
    {
1689
        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...
1690
            unset($this->formGroups[$name]['fields'][$key]);
1691
1692
            if (empty($this->formGroups[$name]['fields'])) {
1693
                unset($this->formGroups[$name]);
1694
            }
1695
        }
1696
    }
1697
1698
    /**
1699
     * @param string $group
1700
     */
1701
    public function reorderFormGroup($group, array $keys)
1702
    {
1703
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1704
        $formGroups = $this->getFormGroups('sonata_deprecation_mute');
1705
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1706
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups('sonata_deprecation_mute') on line 1704 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...
1707
    }
1708
1709
    public function getFormTabs()
1710
    {
1711
        if (!\is_array($this->formTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1712
            @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...
1713
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65.'
1714
                .' It will return only array in version 4.0.',
1715
                __METHOD__
1716
            ), E_USER_DEPRECATED);
1717
        }
1718
1719
        return $this->formTabs;
1720
    }
1721
1722
    public function setFormTabs(array $formTabs)
1723
    {
1724
        $this->formTabs = $formTabs;
1725
    }
1726
1727
    public function getShowTabs()
1728
    {
1729
        if (!\is_array($this->showTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1730
            @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...
1731
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65.'
1732
                .' It will return only array in version 4.0.',
1733
                __METHOD__
1734
            ), E_USER_DEPRECATED);
1735
        }
1736
1737
        return $this->showTabs;
1738
    }
1739
1740
    public function setShowTabs(array $showTabs)
1741
    {
1742
        $this->showTabs = $showTabs;
1743
    }
1744
1745
    public function getShowGroups()
1746
    {
1747
        if (!\is_array($this->showGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1748
            @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...
1749
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65.'
1750
                .' It will return only array in version 4.0.',
1751
                __METHOD__
1752
            ), E_USER_DEPRECATED);
1753
        }
1754
1755
        return $this->showGroups;
1756
    }
1757
1758
    public function setShowGroups(array $showGroups)
1759
    {
1760
        $this->showGroups = $showGroups;
1761
    }
1762
1763
    public function reorderShowGroup($group, array $keys)
1764
    {
1765
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1766
        $showGroups = $this->getShowGroups('sonata_deprecation_mute');
1767
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1768
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups('sonata_deprecation_mute') on line 1766 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...
1769
    }
1770
1771
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
1772
    {
1773
        $this->parentFieldDescription = $parentFieldDescription;
1774
    }
1775
1776
    public function getParentFieldDescription()
1777
    {
1778
        if (!$this->hasParentFieldDescription()) {
1779
            @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...
1780
                'Calling %s() when there is no parent field description is deprecated since'
1781
                .' sonata-project/admin-bundle 3.66 and will throw an exception in 4.0.'
1782
                .' Use %s::hasParentFieldDescription() to know if there is a parent field description.',
1783
                __METHOD__,
1784
                __CLASS__
1785
            ), E_USER_DEPRECATED);
1786
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1787
            // throw new \LogicException(sprintf(
1788
            //    'Admin "%s" has no parent field description.',
1789
            //    static::class
1790
            // ));
1791
1792
            return null;
1793
        }
1794
1795
        return $this->parentFieldDescription;
1796
    }
1797
1798
    public function hasParentFieldDescription()
1799
    {
1800
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1801
    }
1802
1803
    public function setSubject($subject)
1804
    {
1805
        if (\is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1806
            $message = <<<'EOT'
1807
You are trying to set entity an instance of "%s",
1808
which is not the one registered with this admin class ("%s").
1809
This is deprecated since 3.5 and will no longer be supported in 4.0.
1810
EOT;
1811
1812
            // NEXT_MAJOR : throw an exception instead
1813
            @trigger_error(sprintf($message, \get_class($subject), $this->getClass()), E_USER_DEPRECATED);
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...
1814
        }
1815
1816
        $this->subject = $subject;
1817
    }
1818
1819
    public function getSubject()
1820
    {
1821
        if (!$this->hasSubject()) {
1822
            @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...
1823
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.66'
1824
                .' and will throw an exception in 4.0. Use %s::hasSubject() to know if there is a subject.',
1825
                __METHOD__,
1826
                __CLASS__
1827
            ), E_USER_DEPRECATED);
1828
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and update the return type
1829
            // throw new \LogicException(sprintf(
1830
            //    'Admin "%s" has no subject.',
1831
            //    static::class
1832
            // ));
1833
1834
            return null;
1835
        }
1836
1837
        return $this->subject;
1838
    }
1839
1840
    public function hasSubject()
1841
    {
1842
        if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
1843
            $id = $this->request->get($this->getIdParameter());
1844
1845
            if (null !== $id) {
1846
                $this->subject = $this->getObject($id);
1847
            }
1848
        }
1849
1850
        return null !== $this->subject;
1851
    }
1852
1853
    public function getFormFieldDescriptions()
1854
    {
1855
        $this->buildForm();
1856
1857
        return $this->formFieldDescriptions;
1858
    }
1859
1860
    public function getFormFieldDescription($name)
1861
    {
1862
        $this->buildForm();
1863
1864
        if (!$this->hasFormFieldDescription($name)) {
1865
            @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...
1866
                'Calling %s() when there is no form field description is deprecated since'
1867
                .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
1868
                .' Use %s::hasFormFieldDescription() to know if there is a form field description.',
1869
                __METHOD__,
1870
                __CLASS__
1871
            ), E_USER_DEPRECATED);
1872
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1873
            // throw new \LogicException(sprintf(
1874
            //    'Admin "%s" has no form field description for the field %s.',
1875
            //    static::class,
1876
            //    $name
1877
            // ));
1878
1879
            return null;
1880
        }
1881
1882
        return $this->formFieldDescriptions[$name];
1883
    }
1884
1885
    /**
1886
     * Returns true if the admin has a FieldDescription with the given $name.
1887
     *
1888
     * @param string $name
1889
     *
1890
     * @return bool
1891
     */
1892
    public function hasFormFieldDescription($name)
1893
    {
1894
        $this->buildForm();
1895
1896
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1897
    }
1898
1899
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1900
    {
1901
        $this->formFieldDescriptions[$name] = $fieldDescription;
1902
    }
1903
1904
    /**
1905
     * remove a FieldDescription.
1906
     *
1907
     * @param string $name
1908
     */
1909
    public function removeFormFieldDescription($name)
1910
    {
1911
        unset($this->formFieldDescriptions[$name]);
1912
    }
1913
1914
    /**
1915
     * build and return the collection of form FieldDescription.
1916
     *
1917
     * @return FieldDescriptionInterface[] collection of form FieldDescription
1918
     */
1919
    public function getShowFieldDescriptions()
1920
    {
1921
        $this->buildShow();
1922
1923
        return $this->showFieldDescriptions;
1924
    }
1925
1926
    /**
1927
     * Returns the form FieldDescription with the given $name.
1928
     *
1929
     * @param string $name
1930
     *
1931
     * @return FieldDescriptionInterface
1932
     */
1933
    public function getShowFieldDescription($name)
1934
    {
1935
        $this->buildShow();
1936
1937
        if (!$this->hasShowFieldDescription($name)) {
1938
            @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...
1939
                'Calling %s() when there is no show field description is deprecated since'
1940
                .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
1941
                .' Use %s::hasFormFieldDescription() to know if there is a show field description.',
1942
                __METHOD__,
1943
                __CLASS__
1944
            ), E_USER_DEPRECATED);
1945
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1946
            // throw new \LogicException(sprintf(
1947
            //    'Admin "%s" has no show field description for the field %s.',
1948
            //    static::class,
1949
            //    $name
1950
            // ));
1951
1952
            return null;
1953
        }
1954
1955
        return $this->showFieldDescriptions[$name];
1956
    }
1957
1958
    public function hasShowFieldDescription($name)
1959
    {
1960
        $this->buildShow();
1961
1962
        return \array_key_exists($name, $this->showFieldDescriptions);
1963
    }
1964
1965
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1966
    {
1967
        $this->showFieldDescriptions[$name] = $fieldDescription;
1968
    }
1969
1970
    public function removeShowFieldDescription($name)
1971
    {
1972
        unset($this->showFieldDescriptions[$name]);
1973
    }
1974
1975
    public function getListFieldDescriptions()
1976
    {
1977
        $this->buildList();
1978
1979
        return $this->listFieldDescriptions;
1980
    }
1981
1982
    public function getListFieldDescription($name)
1983
    {
1984
        $this->buildList();
1985
1986
        if (!$this->hasListFieldDescription($name)) {
1987
            @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...
1988
                'Calling %s() when there is no list field description is deprecated since'
1989
                .' sonata-project/admin-bundle 3.66 and will throw an exception in 4.0.'
1990
                .' Use %s::hasListFieldDescription(\'%s\') to know if there is a list field description.',
1991
                __METHOD__,
1992
                __CLASS__,
1993
                $name
1994
            ), E_USER_DEPRECATED);
1995
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1996
            // throw new \LogicException(sprintf(
1997
            //    'Admin "%s" has no list field description for %s.',
1998
            //    static::class,
1999
            //    $name
2000
            // ));
2001
2002
            return null;
2003
        }
2004
2005
        return $this->listFieldDescriptions[$name];
2006
    }
2007
2008
    public function hasListFieldDescription($name)
2009
    {
2010
        $this->buildList();
2011
2012
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
2013
    }
2014
2015
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription)
2016
    {
2017
        $this->listFieldDescriptions[$name] = $fieldDescription;
2018
    }
2019
2020
    public function removeListFieldDescription($name)
2021
    {
2022
        unset($this->listFieldDescriptions[$name]);
2023
    }
2024
2025
    public function getFilterFieldDescription($name)
2026
    {
2027
        $this->buildDatagrid();
2028
2029
        if (!$this->hasFilterFieldDescription($name)) {
2030
            @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...
2031
                'Calling %s() when there is no filter field description is deprecated since'
2032
                .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
2033
                .' Use %s::hasFilterFieldDescription() to know if there is a filter field description.',
2034
                __METHOD__,
2035
                __CLASS__
2036
            ), E_USER_DEPRECATED);
2037
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
2038
            // throw new \LogicException(sprintf(
2039
            //    'Admin "%s" has no filter field description for the field %s.',
2040
            //    static::class,
2041
            //    $name
2042
            // ));
2043
2044
            return null;
2045
        }
2046
2047
        return $this->filterFieldDescriptions[$name];
2048
    }
2049
2050
    public function hasFilterFieldDescription($name)
2051
    {
2052
        $this->buildDatagrid();
2053
2054
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
2055
    }
2056
2057
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription)
2058
    {
2059
        $this->filterFieldDescriptions[$name] = $fieldDescription;
2060
    }
2061
2062
    public function removeFilterFieldDescription($name)
2063
    {
2064
        unset($this->filterFieldDescriptions[$name]);
2065
    }
2066
2067
    public function getFilterFieldDescriptions()
2068
    {
2069
        $this->buildDatagrid();
2070
2071
        return $this->filterFieldDescriptions;
2072
    }
2073
2074
    public function addChild(AdminInterface $child)
2075
    {
2076
        $parentAdmin = $this;
2077
        while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
2078
            $parentAdmin = $parentAdmin->getParent();
2079
        }
2080
2081
        if ($parentAdmin->getCode() === $child->getCode()) {
2082
            throw new \RuntimeException(sprintf(
2083
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
2084
                $child->getCode(),
2085
                $this->getCode()
2086
            ));
2087
        }
2088
2089
        $this->children[$child->getCode()] = $child;
2090
2091
        $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...
2092
2093
        // NEXT_MAJOR: remove $args and add $field parameter to this function on next Major
2094
2095
        $args = \func_get_args();
2096
2097
        if (isset($args[1])) {
2098
            $child->addParentAssociationMapping($this->getCode(), $args[1]);
2099
        } else {
2100
            @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...
2101
                'Calling "addChild" without second argument is deprecated since sonata-project/admin-bundle 3.35 and will not be allowed in 4.0.',
2102
                E_USER_DEPRECATED
2103
            );
2104
        }
2105
    }
2106
2107
    public function hasChild($code)
2108
    {
2109
        return isset($this->children[$code]);
2110
    }
2111
2112
    public function getChildren()
2113
    {
2114
        return $this->children;
2115
    }
2116
2117
    public function getChild($code)
2118
    {
2119
        if (!$this->hasChild($code)) {
2120
            @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...
2121
                'Calling %s() when there is no child is deprecated since sonata-project/admin-bundle 3.69'
2122
                .' and will throw an exception in 4.0. Use %s::hasChild() to know if the child exists.',
2123
                __METHOD__,
2124
                __CLASS__
2125
            ), E_USER_DEPRECATED);
2126
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
2127
            // throw new \LogicException(sprintf(
2128
            //    'Admin "%s" has no child for the code %s.',
2129
            //    static::class,
2130
            //    $code
2131
            // ));
2132
2133
            return null;
2134
        }
2135
2136
        return $this->children[$code];
2137
    }
2138
2139
    public function setParent(AdminInterface $parent)
2140
    {
2141
        $this->parent = $parent;
2142
    }
2143
2144
    public function getParent()
2145
    {
2146
        if (!$this->isChild()) {
2147
            @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...
2148
                'Calling %s() when there is no parent is deprecated since sonata-project/admin-bundle 3.66'
2149
                .' and will throw an exception in 4.0. Use %s::isChild() to know if there is a parent.',
2150
                __METHOD__,
2151
                __CLASS__
2152
            ), E_USER_DEPRECATED);
2153
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
2154
            // throw new \LogicException(sprintf(
2155
            //    'Admin "%s" has no parent.',
2156
            //    static::class
2157
            // ));
2158
2159
            return null;
2160
        }
2161
2162
        return $this->parent;
2163
    }
2164
2165
    final public function getRootAncestor()
2166
    {
2167
        $parent = $this;
2168
2169
        while ($parent->isChild()) {
2170
            $parent = $parent->getParent();
2171
        }
2172
2173
        return $parent;
2174
    }
2175
2176
    final public function getChildDepth()
2177
    {
2178
        $parent = $this;
2179
        $depth = 0;
2180
2181
        while ($parent->isChild()) {
2182
            $parent = $parent->getParent();
2183
            ++$depth;
2184
        }
2185
2186
        return $depth;
2187
    }
2188
2189
    final public function getCurrentLeafChildAdmin()
2190
    {
2191
        $child = $this->getCurrentChildAdmin();
2192
2193
        if (null === $child) {
2194
            return null;
2195
        }
2196
2197
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
2198
            $child = $c;
2199
        }
2200
2201
        return $child;
2202
    }
2203
2204
    public function isChild()
2205
    {
2206
        return $this->parent instanceof AdminInterface;
2207
    }
2208
2209
    /**
2210
     * Returns true if the admin has children, false otherwise.
2211
     *
2212
     * @return bool if the admin has children
2213
     */
2214
    public function hasChildren()
2215
    {
2216
        return \count($this->children) > 0;
2217
    }
2218
2219
    public function setUniqid($uniqid)
2220
    {
2221
        $this->uniqid = $uniqid;
2222
    }
2223
2224
    public function getUniqid()
2225
    {
2226
        if (!$this->uniqid) {
2227
            $this->uniqid = sprintf('s%s', uniqid());
2228
        }
2229
2230
        return $this->uniqid;
2231
    }
2232
2233
    /**
2234
     * Returns the classname label.
2235
     *
2236
     * @return string the classname label
2237
     */
2238
    public function getClassnameLabel()
2239
    {
2240
        return $this->classnameLabel;
2241
    }
2242
2243
    public function getPersistentParameters()
2244
    {
2245
        $parameters = [];
2246
2247
        foreach ($this->getExtensions() as $extension) {
2248
            $params = $extension->getPersistentParameters($this);
2249
2250
            if (!\is_array($params)) {
2251
                throw new \RuntimeException(sprintf(
2252
                    'The %s::getPersistentParameters must return an array',
2253
                    \get_class($extension)
2254
                ));
2255
            }
2256
2257
            $parameters = array_merge($parameters, $params);
2258
        }
2259
2260
        return $parameters;
2261
    }
2262
2263
    /**
2264
     * @param string $name
2265
     *
2266
     * @return mixed|null
2267
     */
2268
    public function getPersistentParameter($name)
2269
    {
2270
        $parameters = $this->getPersistentParameters();
2271
2272
        return $parameters[$name] ?? null;
2273
    }
2274
2275
    public function getBreadcrumbs($action)
2276
    {
2277
        @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...
2278
            'The %s method is deprecated since version 3.2 and will be removed in 4.0.'
2279
            .' Use %s::getBreadcrumbs instead.',
2280
            __METHOD__,
2281
            BreadcrumbsBuilder::class
2282
        ), E_USER_DEPRECATED);
2283
2284
        return $this->getBreadcrumbsBuilder()->getBreadcrumbs($this, $action);
2285
    }
2286
2287
    /**
2288
     * Generates the breadcrumbs array.
2289
     *
2290
     * Note: the method will be called by the top admin instance (parent => child)
2291
     *
2292
     * @param string $action
2293
     *
2294
     * @return array
2295
     */
2296
    public function buildBreadcrumbs($action, ?ItemInterface $menu = null)
2297
    {
2298
        @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...
2299
            'The %s method is deprecated since version 3.2 and will be removed in 4.0.',
2300
            __METHOD__
2301
        ), E_USER_DEPRECATED);
2302
2303
        if (isset($this->breadcrumbs[$action])) {
2304
            return $this->breadcrumbs[$action];
2305
        }
2306
2307
        return $this->breadcrumbs[$action] = $this->getBreadcrumbsBuilder()
2308
            ->buildBreadcrumbs($this, $action, $menu);
2309
    }
2310
2311
    /**
2312
     * NEXT_MAJOR : remove this method.
2313
     *
2314
     * @return BreadcrumbsBuilderInterface
2315
     */
2316
    final public function getBreadcrumbsBuilder()
2317
    {
2318
        @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...
2319
            'The %s method is deprecated since version 3.2 and will be removed in 4.0.'
2320
            .' Use the sonata.admin.breadcrumbs_builder service instead.',
2321
            __METHOD__
2322
        ), E_USER_DEPRECATED);
2323
        if (null === $this->breadcrumbsBuilder) {
2324
            $this->breadcrumbsBuilder = new BreadcrumbsBuilder(
2325
                $this->getConfigurationPool()->getContainer()->getParameter('sonata.admin.configuration.breadcrumbs')
2326
            );
2327
        }
2328
2329
        return $this->breadcrumbsBuilder;
2330
    }
2331
2332
    /**
2333
     * NEXT_MAJOR : remove this method.
2334
     *
2335
     * @return AbstractAdmin
2336
     */
2337
    final public function setBreadcrumbsBuilder(BreadcrumbsBuilderInterface $value)
2338
    {
2339
        @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...
2340
            'The %s method is deprecated since version 3.2 and will be removed in 4.0.'
2341
            .' Use the sonata.admin.breadcrumbs_builder service instead.',
2342
            __METHOD__
2343
        ), E_USER_DEPRECATED);
2344
        $this->breadcrumbsBuilder = $value;
2345
2346
        return $this;
2347
    }
2348
2349
    public function setCurrentChild($currentChild)
2350
    {
2351
        $this->currentChild = $currentChild;
2352
    }
2353
2354
    /**
2355
     * NEXT_MAJOR: Remove this method.
2356
     *
2357
     * @deprecated since sonata-project/admin-bundle 3.65, to be removed in 4.0
2358
     */
2359
    public function getCurrentChild()
2360
    {
2361
        @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...
2362
            'The %s() method is deprecated since version 3.65 and will be removed in 4.0.'
2363
            .' Use %s::isCurrentChild() instead.',
2364
            __METHOD__,
2365
            __CLASS__
2366
        ), E_USER_DEPRECATED);
2367
2368
        return $this->currentChild;
2369
    }
2370
2371
    public function isCurrentChild(): bool
2372
    {
2373
        return $this->currentChild;
2374
    }
2375
2376
    /**
2377
     * Returns the current child admin instance.
2378
     *
2379
     * @return AdminInterface|null the current child admin instance
2380
     */
2381
    public function getCurrentChildAdmin()
2382
    {
2383
        foreach ($this->children as $children) {
2384
            if ($children->isCurrentChild()) {
2385
                return $children;
2386
            }
2387
        }
2388
2389
        return null;
2390
    }
2391
2392
    public function trans($id, array $parameters = [], $domain = null, $locale = null)
2393
    {
2394
        @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...
2395
            'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2396
            __METHOD__
2397
        ), E_USER_DEPRECATED);
2398
2399
        $domain = $domain ?: $this->getTranslationDomain();
2400
2401
        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...
2402
    }
2403
2404
    /**
2405
     * Translate a message id.
2406
     *
2407
     * NEXT_MAJOR: remove this method
2408
     *
2409
     * @param string      $id
2410
     * @param int         $count
2411
     * @param string|null $domain
2412
     * @param string|null $locale
2413
     *
2414
     * @return string the translated string
2415
     *
2416
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2417
     */
2418
    public function transChoice($id, $count, array $parameters = [], $domain = null, $locale = null)
2419
    {
2420
        @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...
2421
            'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2422
            __METHOD__
2423
        ), E_USER_DEPRECATED);
2424
2425
        $domain = $domain ?: $this->getTranslationDomain();
2426
2427
        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...
2428
    }
2429
2430
    public function setTranslationDomain($translationDomain)
2431
    {
2432
        $this->translationDomain = $translationDomain;
2433
    }
2434
2435
    public function getTranslationDomain()
2436
    {
2437
        return $this->translationDomain;
2438
    }
2439
2440
    /**
2441
     * {@inheritdoc}
2442
     *
2443
     * NEXT_MAJOR: remove this method
2444
     *
2445
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2446
     */
2447
    public function setTranslator(TranslatorInterface $translator)
2448
    {
2449
        $args = \func_get_args();
2450
        if (isset($args[1]) && $args[1]) {
2451
            @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...
2452
                'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2453
                __METHOD__
2454
            ), E_USER_DEPRECATED);
2455
        }
2456
2457
        $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...
2458
    }
2459
2460
    /**
2461
     * {@inheritdoc}
2462
     *
2463
     * NEXT_MAJOR: remove this method
2464
     *
2465
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2466
     */
2467
    public function getTranslator()
2468
    {
2469
        @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...
2470
            'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2471
            __METHOD__
2472
        ), E_USER_DEPRECATED);
2473
2474
        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...
2475
    }
2476
2477
    public function getTranslationLabel($label, $context = '', $type = '')
2478
    {
2479
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2480
    }
2481
2482
    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...
2483
    {
2484
        $this->request = $request;
2485
2486
        foreach ($this->getChildren() as $children) {
2487
            $children->setRequest($request);
2488
        }
2489
    }
2490
2491
    public function getRequest()
2492
    {
2493
        if (!$this->request) {
2494
            // NEXT_MAJOR: Throw \LogicException instead.
2495
            throw new \RuntimeException('The Request object has not been set');
2496
        }
2497
2498
        return $this->request;
2499
    }
2500
2501
    public function hasRequest()
2502
    {
2503
        return null !== $this->request;
2504
    }
2505
2506
    public function setFormContractor(FormContractorInterface $formBuilder)
2507
    {
2508
        $this->formContractor = $formBuilder;
2509
    }
2510
2511
    /**
2512
     * @return FormContractorInterface
2513
     */
2514
    public function getFormContractor()
2515
    {
2516
        return $this->formContractor;
2517
    }
2518
2519
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder)
2520
    {
2521
        $this->datagridBuilder = $datagridBuilder;
2522
    }
2523
2524
    public function getDatagridBuilder()
2525
    {
2526
        return $this->datagridBuilder;
2527
    }
2528
2529
    public function setListBuilder(ListBuilderInterface $listBuilder)
2530
    {
2531
        $this->listBuilder = $listBuilder;
2532
    }
2533
2534
    public function getListBuilder()
2535
    {
2536
        return $this->listBuilder;
2537
    }
2538
2539
    public function setShowBuilder(ShowBuilderInterface $showBuilder)
2540
    {
2541
        $this->showBuilder = $showBuilder;
2542
    }
2543
2544
    /**
2545
     * @return ShowBuilderInterface
2546
     */
2547
    public function getShowBuilder()
2548
    {
2549
        return $this->showBuilder;
2550
    }
2551
2552
    public function setConfigurationPool(Pool $configurationPool)
2553
    {
2554
        $this->configurationPool = $configurationPool;
2555
    }
2556
2557
    /**
2558
     * @return Pool
2559
     */
2560
    public function getConfigurationPool()
2561
    {
2562
        return $this->configurationPool;
2563
    }
2564
2565
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator)
2566
    {
2567
        $this->routeGenerator = $routeGenerator;
2568
    }
2569
2570
    /**
2571
     * @return RouteGeneratorInterface
2572
     */
2573
    public function getRouteGenerator()
2574
    {
2575
        return $this->routeGenerator;
2576
    }
2577
2578
    public function getCode()
2579
    {
2580
        return $this->code;
2581
    }
2582
2583
    /**
2584
     * NEXT_MAJOR: Remove this function.
2585
     *
2586
     * @deprecated This method is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0
2587
     *
2588
     * @param string $baseCodeRoute
2589
     */
2590
    public function setBaseCodeRoute($baseCodeRoute)
2591
    {
2592
        @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...
2593
            'The %s is deprecated since 3.24 and will be removed in 4.0.',
2594
            __METHOD__
2595
        ), E_USER_DEPRECATED);
2596
2597
        $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...
2598
    }
2599
2600
    public function getBaseCodeRoute()
2601
    {
2602
        // NEXT_MAJOR: Uncomment the following lines.
2603
        // if ($this->isChild()) {
2604
        //     return sprintf('%s|%s', $this->getParent()->getBaseCodeRoute(), $this->getCode());
2605
        // }
2606
        //
2607
        // return $this->getCode();
2608
2609
        // NEXT_MAJOR: Remove all the code below.
2610
        if ($this->isChild()) {
2611
            $parentCode = $this->getParent()->getCode();
2612
2613
            if ($this->getParent()->isChild()) {
2614
                $parentCode = $this->getParent()->getBaseCodeRoute();
2615
            }
2616
2617
            return sprintf('%s|%s', $parentCode, $this->getCode());
2618
        }
2619
2620
        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...
2621
    }
2622
2623
    public function getModelManager()
2624
    {
2625
        return $this->modelManager;
2626
    }
2627
2628
    public function setModelManager(ModelManagerInterface $modelManager)
2629
    {
2630
        $this->modelManager = $modelManager;
2631
    }
2632
2633
    public function getManagerType()
2634
    {
2635
        return $this->managerType;
2636
    }
2637
2638
    /**
2639
     * @param string $type
2640
     */
2641
    public function setManagerType($type)
2642
    {
2643
        $this->managerType = $type;
2644
    }
2645
2646
    public function getObjectIdentifier()
2647
    {
2648
        return $this->getCode();
2649
    }
2650
2651
    /**
2652
     * Set the roles and permissions per role.
2653
     */
2654
    public function setSecurityInformation(array $information)
2655
    {
2656
        $this->securityInformation = $information;
2657
    }
2658
2659
    public function getSecurityInformation()
2660
    {
2661
        return $this->securityInformation;
2662
    }
2663
2664
    /**
2665
     * Return the list of permissions the user should have in order to display the admin.
2666
     *
2667
     * @param string $context
2668
     *
2669
     * @return array
2670
     */
2671
    public function getPermissionsShow($context)
2672
    {
2673
        switch ($context) {
2674
            case self::CONTEXT_DASHBOARD:
2675
            case self::CONTEXT_MENU:
2676
            default:
2677
                return ['LIST'];
2678
        }
2679
    }
2680
2681
    public function showIn($context)
2682
    {
2683
        switch ($context) {
2684
            case self::CONTEXT_DASHBOARD:
2685
            case self::CONTEXT_MENU:
2686
            default:
2687
                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...
2688
        }
2689
    }
2690
2691
    public function createObjectSecurity($object)
2692
    {
2693
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2694
    }
2695
2696
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
2697
    {
2698
        $this->securityHandler = $securityHandler;
2699
    }
2700
2701
    public function getSecurityHandler()
2702
    {
2703
        return $this->securityHandler;
2704
    }
2705
2706
    public function isGranted($name, $object = null)
2707
    {
2708
        $objectRef = $object ? sprintf('/%s#%s', spl_object_hash($object), $this->id($object)) : '';
2709
        $key = md5(json_encode($name).$objectRef);
2710
2711
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
2712
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2713
        }
2714
2715
        return $this->cacheIsGranted[$key];
2716
    }
2717
2718
    public function getUrlSafeIdentifier($model)
2719
    {
2720
        return $this->getModelManager()->getUrlSafeIdentifier($model);
2721
    }
2722
2723
    public function getNormalizedIdentifier($model)
2724
    {
2725
        return $this->getModelManager()->getNormalizedIdentifier($model);
2726
    }
2727
2728
    public function id($model)
2729
    {
2730
        return $this->getNormalizedIdentifier($model);
2731
    }
2732
2733
    public function setValidator($validator)
2734
    {
2735
        // NEXT_MAJOR: Move ValidatorInterface check to method signature
2736
        if (!$validator instanceof ValidatorInterface) {
2737
            throw new \InvalidArgumentException(sprintf(
2738
                'Argument 1 must be an instance of %s',
2739
                ValidatorInterface::class
2740
            ));
2741
        }
2742
2743
        $this->validator = $validator;
2744
    }
2745
2746
    public function getValidator()
2747
    {
2748
        return $this->validator;
2749
    }
2750
2751
    public function getShow()
2752
    {
2753
        $this->buildShow();
2754
2755
        return $this->show;
2756
    }
2757
2758
    public function setFormTheme(array $formTheme)
2759
    {
2760
        $this->formTheme = $formTheme;
2761
    }
2762
2763
    public function getFormTheme()
2764
    {
2765
        return $this->formTheme;
2766
    }
2767
2768
    public function setFilterTheme(array $filterTheme)
2769
    {
2770
        $this->filterTheme = $filterTheme;
2771
    }
2772
2773
    public function getFilterTheme()
2774
    {
2775
        return $this->filterTheme;
2776
    }
2777
2778
    public function addExtension(AdminExtensionInterface $extension)
2779
    {
2780
        $this->extensions[] = $extension;
2781
    }
2782
2783
    public function getExtensions()
2784
    {
2785
        return $this->extensions;
2786
    }
2787
2788
    public function setMenuFactory(FactoryInterface $menuFactory)
2789
    {
2790
        $this->menuFactory = $menuFactory;
2791
    }
2792
2793
    public function getMenuFactory()
2794
    {
2795
        return $this->menuFactory;
2796
    }
2797
2798
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder)
2799
    {
2800
        $this->routeBuilder = $routeBuilder;
2801
    }
2802
2803
    public function getRouteBuilder()
2804
    {
2805
        return $this->routeBuilder;
2806
    }
2807
2808
    public function toString($object)
2809
    {
2810
        if (!\is_object($object)) {
2811
            return '';
2812
        }
2813
2814
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2815
            return (string) $object;
2816
        }
2817
2818
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2819
    }
2820
2821
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy)
2822
    {
2823
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2824
    }
2825
2826
    public function getLabelTranslatorStrategy()
2827
    {
2828
        return $this->labelTranslatorStrategy;
2829
    }
2830
2831
    public function supportsPreviewMode()
2832
    {
2833
        return $this->supportsPreviewMode;
2834
    }
2835
2836
    /**
2837
     * NEXT_MAJOR: Remove this.
2838
     *
2839
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
2840
     *
2841
     * Set custom per page options.
2842
     */
2843
    public function setPerPageOptions(array $options)
2844
    {
2845
        @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...
2846
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
2847
            __METHOD__
2848
        ), E_USER_DEPRECATED);
2849
2850
        $this->perPageOptions = $options;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$perPageOptions has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
2851
    }
2852
2853
    /**
2854
     * Returns predefined per page options.
2855
     *
2856
     * @return array
2857
     */
2858
    public function getPerPageOptions()
2859
    {
2860
        // NEXT_MAJOR: Remove this line and uncomment the following
2861
        return $this->perPageOptions;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$perPageOptions has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
2862
//        $perPageOptions = $this->getModelManager()->getDefaultPerPageOptions($this->class);
2863
//        $perPageOptions[] = $this->getMaxPerPage();
2864
//
2865
//        $perPageOptions = array_unique($perPageOptions);
2866
//        sort($perPageOptions);
2867
//
2868
//        return $perPageOptions;
2869
    }
2870
2871
    /**
2872
     * Set pager type.
2873
     *
2874
     * @param string $pagerType
2875
     */
2876
    public function setPagerType($pagerType)
2877
    {
2878
        $this->pagerType = $pagerType;
2879
    }
2880
2881
    /**
2882
     * Get pager type.
2883
     *
2884
     * @return string
2885
     */
2886
    public function getPagerType()
2887
    {
2888
        return $this->pagerType;
2889
    }
2890
2891
    /**
2892
     * Returns true if the per page value is allowed, false otherwise.
2893
     *
2894
     * @param int $perPage
2895
     *
2896
     * @return bool
2897
     */
2898
    public function determinedPerPageValue($perPage)
2899
    {
2900
        return \in_array($perPage, $this->getPerPageOptions(), true);
2901
    }
2902
2903
    public function isAclEnabled()
2904
    {
2905
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2906
    }
2907
2908
    public function getObjectMetadata($object)
2909
    {
2910
        return new Metadata($this->toString($object));
2911
    }
2912
2913
    public function getListModes()
2914
    {
2915
        return $this->listModes;
2916
    }
2917
2918
    public function setListMode($mode)
2919
    {
2920
        if (!$this->hasRequest()) {
2921
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2922
        }
2923
2924
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2925
    }
2926
2927
    public function getListMode()
2928
    {
2929
        if (!$this->hasRequest()) {
2930
            return 'list';
2931
        }
2932
2933
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2934
    }
2935
2936
    public function getAccessMapping()
2937
    {
2938
        return $this->accessMapping;
2939
    }
2940
2941
    public function checkAccess($action, $object = null)
2942
    {
2943
        $access = $this->getAccess();
2944
2945
        if (!\array_key_exists($action, $access)) {
2946
            throw new \InvalidArgumentException(sprintf(
2947
                'Action "%s" could not be found in access mapping.'
2948
                .' Please make sure your action is defined into your admin class accessMapping property.',
2949
                $action
2950
            ));
2951
        }
2952
2953
        if (!\is_array($access[$action])) {
2954
            $access[$action] = [$access[$action]];
2955
        }
2956
2957
        foreach ($access[$action] as $role) {
2958
            if (false === $this->isGranted($role, $object)) {
2959
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2960
            }
2961
        }
2962
    }
2963
2964
    /**
2965
     * Hook to handle access authorization, without throw Exception.
2966
     *
2967
     * @param string $action
2968
     * @param object $object
2969
     *
2970
     * @return bool
2971
     */
2972
    public function hasAccess($action, $object = null)
2973
    {
2974
        $access = $this->getAccess();
2975
2976
        if (!\array_key_exists($action, $access)) {
2977
            return false;
2978
        }
2979
2980
        if (!\is_array($access[$action])) {
2981
            $access[$action] = [$access[$action]];
2982
        }
2983
2984
        foreach ($access[$action] as $role) {
2985
            if (false === $this->isGranted($role, $object)) {
2986
                return false;
2987
            }
2988
        }
2989
2990
        return true;
2991
    }
2992
2993
    /**
2994
     * @param string      $action
2995
     * @param object|null $object
2996
     *
2997
     * @return array
2998
     */
2999
    public function configureActionButtons($action, $object = null)
3000
    {
3001
        $list = [];
3002
3003
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
3004
            && $this->hasAccess('create')
3005
            && $this->hasRoute('create')
3006
        ) {
3007
            $list['create'] = [
3008
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3009
                '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...
3010
//                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
3011
            ];
3012
        }
3013
3014
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
3015
            && $this->canAccessObject('edit', $object)
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 2999 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...
3016
            && $this->hasRoute('edit')
3017
        ) {
3018
            $list['edit'] = [
3019
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3020
                '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...
3021
                //'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
3022
            ];
3023
        }
3024
3025
        if (\in_array($action, ['show', 'edit', 'acl'], true)
3026
            && $this->canAccessObject('history', $object)
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 2999 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...
3027
            && $this->hasRoute('history')
3028
        ) {
3029
            $list['history'] = [
3030
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3031
                '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...
3032
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
3033
            ];
3034
        }
3035
3036
        if (\in_array($action, ['edit', 'history'], true)
3037
            && $this->isAclEnabled()
3038
            && $this->canAccessObject('acl', $object)
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 2999 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...
3039
            && $this->hasRoute('acl')
3040
        ) {
3041
            $list['acl'] = [
3042
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3043
                '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...
3044
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
3045
            ];
3046
        }
3047
3048
        if (\in_array($action, ['edit', 'history', 'acl'], true)
3049
            && $this->canAccessObject('show', $object)
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 2999 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...
3050
            && \count($this->getShow()) > 0
3051
            && $this->hasRoute('show')
3052
        ) {
3053
            $list['show'] = [
3054
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3055
                '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...
3056
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
3057
            ];
3058
        }
3059
3060
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
3061
            && $this->hasAccess('list')
3062
            && $this->hasRoute('list')
3063
        ) {
3064
            $list['list'] = [
3065
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3066
                '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...
3067
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
3068
            ];
3069
        }
3070
3071
        return $list;
3072
    }
3073
3074
    /**
3075
     * @param string $action
3076
     * @param object $object
3077
     *
3078
     * @return array
3079
     */
3080
    public function getActionButtons($action, $object = null)
3081
    {
3082
        $list = $this->configureActionButtons($action, $object);
3083
3084
        foreach ($this->getExtensions() as $extension) {
3085
            // NEXT_MAJOR: remove method check
3086
            if (method_exists($extension, 'configureActionButtons')) {
3087
                $list = $extension->configureActionButtons($this, $list, $action, $object);
3088
            }
3089
        }
3090
3091
        return $list;
3092
    }
3093
3094
    /**
3095
     * Get the list of actions that can be accessed directly from the dashboard.
3096
     *
3097
     * @return array
3098
     */
3099
    public function getDashboardActions()
3100
    {
3101
        $actions = [];
3102
3103
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
3104
            $actions['create'] = [
3105
                'label' => 'link_add',
3106
                'translation_domain' => 'SonataAdminBundle',
3107
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3108
                '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...
3109
                // 'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
3110
                'url' => $this->generateUrl('create'),
3111
                'icon' => 'plus-circle',
3112
            ];
3113
        }
3114
3115
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
3116
            $actions['list'] = [
3117
                'label' => 'link_list',
3118
                'translation_domain' => 'SonataAdminBundle',
3119
                'url' => $this->generateUrl('list'),
3120
                'icon' => 'list',
3121
            ];
3122
        }
3123
3124
        return $actions;
3125
    }
3126
3127
    /**
3128
     * Setting to true will enable mosaic button for the admin screen.
3129
     * Setting to false will hide mosaic button for the admin screen.
3130
     *
3131
     * @param bool $isShown
3132
     */
3133
    final public function showMosaicButton($isShown)
3134
    {
3135
        if ($isShown) {
3136
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
3137
        } else {
3138
            unset($this->listModes['mosaic']);
3139
        }
3140
    }
3141
3142
    /**
3143
     * @param object $object
3144
     */
3145
    final public function getSearchResultLink($object)
3146
    {
3147
        foreach ($this->searchResultActions as $action) {
3148
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
3149
                return $this->generateObjectUrl($action, $object);
3150
            }
3151
        }
3152
3153
        return null;
3154
    }
3155
3156
    /**
3157
     * Checks if a filter type is set to a default value.
3158
     *
3159
     * @param string $name
3160
     *
3161
     * @return bool
3162
     */
3163
    final public function isDefaultFilter($name)
3164
    {
3165
        $filter = $this->getFilterParameters();
3166
        $default = $this->getDefaultFilterValues();
3167
3168
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
3169
            return false;
3170
        }
3171
3172
        return $filter[$name] === $default[$name];
3173
    }
3174
3175
    /**
3176
     * Check object existence and access, without throw Exception.
3177
     *
3178
     * @param string $action
3179
     * @param object $object
3180
     *
3181
     * @return bool
3182
     */
3183
    public function canAccessObject($action, $object)
3184
    {
3185
        return $object && $this->id($object) && $this->hasAccess($action, $object);
3186
    }
3187
3188
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
3189
    {
3190
        return $query;
3191
    }
3192
3193
    /**
3194
     * @return MutableTemplateRegistryInterface
3195
     */
3196
    final protected function getTemplateRegistry()
3197
    {
3198
        return $this->templateRegistry;
3199
    }
3200
3201
    /**
3202
     * Returns a list of default sort values.
3203
     *
3204
     * @return array{_page?: int, _per_page?: int, _sort_by?: string, _sort_order?: string}
0 ignored issues
show
Documentation introduced by
The doc-type array{_page?: could not be parsed: Unknown type name "array{_page?:" at position 0. (view supported doc-types)

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

Loading history...
3205
     */
3206
    final protected function getDefaultSortValues(): array
3207
    {
3208
        $defaultSortValues = [];
3209
3210
        $this->configureDefaultSortValues($defaultSortValues);
3211
3212
        foreach ($this->getExtensions() as $extension) {
3213
            // NEXT_MAJOR: remove method check
3214
            if (method_exists($extension, 'configureDefaultSortValues')) {
3215
                $extension->configureDefaultSortValues($this, $defaultSortValues);
3216
            }
3217
        }
3218
3219
        return $defaultSortValues;
3220
    }
3221
3222
    /**
3223
     * Returns a list of default filters.
3224
     *
3225
     * @return array
3226
     */
3227
    final protected function getDefaultFilterValues()
3228
    {
3229
        $defaultFilterValues = [];
3230
3231
        $this->configureDefaultFilterValues($defaultFilterValues);
3232
3233
        foreach ($this->getExtensions() as $extension) {
3234
            // NEXT_MAJOR: remove method check
3235
            if (method_exists($extension, 'configureDefaultFilterValues')) {
3236
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
3237
            }
3238
        }
3239
3240
        return $defaultFilterValues;
3241
    }
3242
3243
    protected function configureFormFields(FormMapper $form)
3244
    {
3245
    }
3246
3247
    protected function configureListFields(ListMapper $list)
3248
    {
3249
    }
3250
3251
    protected function configureDatagridFilters(DatagridMapper $filter)
3252
    {
3253
    }
3254
3255
    protected function configureShowFields(ShowMapper $show)
3256
    {
3257
    }
3258
3259
    protected function configureRoutes(RouteCollection $collection)
3260
    {
3261
    }
3262
3263
    /**
3264
     * Allows you to customize batch actions.
3265
     *
3266
     * @param array $actions List of actions
3267
     *
3268
     * @return array
3269
     */
3270
    protected function configureBatchActions($actions)
3271
    {
3272
        return $actions;
3273
    }
3274
3275
    /**
3276
     * NEXT_MAJOR: remove this method.
3277
     *
3278
     * @deprecated Use configureTabMenu instead
3279
     */
3280
    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...
3281
    {
3282
    }
3283
3284
    /**
3285
     * Configures the tab menu in your admin.
3286
     *
3287
     * @param string $action
3288
     */
3289
    protected function configureTabMenu(ItemInterface $menu, $action, ?AdminInterface $childAdmin = null)
3290
    {
3291
        // Use configureSideMenu not to mess with previous overrides
3292
        // NEXT_MAJOR: remove this line
3293
        $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...
3294
    }
3295
3296
    /**
3297
     * build the view FieldDescription array.
3298
     */
3299
    protected function buildShow()
3300
    {
3301
        if ($this->loaded['show']) {
3302
            return;
3303
        }
3304
3305
        $this->loaded['show'] = true;
3306
3307
        $this->show = $this->getShowBuilder()->getBaseList();
3308
        $mapper = new ShowMapper($this->getShowBuilder(), $this->show, $this);
3309
3310
        $this->configureShowFields($mapper);
3311
3312
        foreach ($this->getExtensions() as $extension) {
3313
            $extension->configureShowFields($mapper);
3314
        }
3315
    }
3316
3317
    /**
3318
     * build the list FieldDescription array.
3319
     */
3320
    protected function buildList()
3321
    {
3322
        if ($this->loaded['list']) {
3323
            return;
3324
        }
3325
3326
        $this->loaded['list'] = true;
3327
3328
        $this->list = $this->getListBuilder()->getBaseList();
3329
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
3330
3331
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
3332
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3333
                $this->getClass(),
3334
                'batch',
3335
                [
3336
                    'label' => 'batch',
3337
                    'code' => '_batch',
3338
                    'sortable' => false,
3339
                    'virtual_field' => true,
3340
                ]
3341
            );
3342
3343
            $fieldDescription->setAdmin($this);
3344
            // NEXT_MAJOR: Remove this line and use commented line below it instead
3345
            $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...
3346
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
3347
3348
            $mapper->add($fieldDescription, ListMapper::TYPE_BATCH);
3349
        }
3350
3351
        $this->configureListFields($mapper);
3352
3353
        foreach ($this->getExtensions() as $extension) {
3354
            $extension->configureListFields($mapper);
3355
        }
3356
3357
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
3358
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3359
                $this->getClass(),
3360
                'select',
3361
                [
3362
                    'label' => false,
3363
                    'code' => '_select',
3364
                    'sortable' => false,
3365
                    'virtual_field' => false,
3366
                ]
3367
            );
3368
3369
            $fieldDescription->setAdmin($this);
3370
            // NEXT_MAJOR: Remove this line and use commented line below it instead
3371
            $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...
3372
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
3373
3374
            $mapper->add($fieldDescription, ListMapper::TYPE_SELECT);
3375
        }
3376
    }
3377
3378
    /**
3379
     * Build the form FieldDescription collection.
3380
     */
3381
    protected function buildForm()
3382
    {
3383
        if ($this->loaded['form']) {
3384
            return;
3385
        }
3386
3387
        $this->loaded['form'] = true;
3388
3389
        $formBuilder = $this->getFormBuilder();
3390
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
3391
            $this->preValidate($event->getData());
3392
        }, 100);
3393
3394
        $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...
3395
    }
3396
3397
    /**
3398
     * Gets the subclass corresponding to the given name.
3399
     *
3400
     * @param string $name The name of the sub class
3401
     *
3402
     * @return string the subclass
3403
     */
3404
    protected function getSubClass($name)
3405
    {
3406
        if ($this->hasSubClass($name)) {
3407
            return $this->subClasses[$name];
3408
        }
3409
3410
        // NEXT_MAJOR: Throw \LogicException instead.
3411
        throw new \RuntimeException(sprintf('Unable to find the subclass `%s` for admin `%s`', $name, static::class));
3412
    }
3413
3414
    /**
3415
     * Attach the inline validator to the model metadata, this must be done once per admin.
3416
     */
3417
    protected function attachInlineValidator()
3418
    {
3419
        $admin = $this;
3420
3421
        // add the custom inline validation option
3422
        $metadata = $this->validator->getMetadataFor($this->getClass());
3423
        if (!$metadata instanceof GenericMetadata) {
3424
            throw new \UnexpectedValueException(
3425
                sprintf(
3426
                    'Cannot add inline validator for %s because its metadata is an instance of %s instead of %s',
3427
                    $this->getClass(),
3428
                    \get_class($metadata),
3429
                    GenericMetadata::class
3430
                )
3431
            );
3432
        }
3433
3434
        $metadata->addConstraint(new InlineConstraint([
3435
            'service' => $this,
3436
            'method' => static function (ErrorElement $errorElement, $object) use ($admin) {
3437
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
3438
3439
                // This avoid the main validation to be cascaded to children
3440
                // The problem occurs when a model Page has a collection of Page as property
3441
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
3442
                    return;
3443
                }
3444
3445
                $admin->validate($errorElement, $object);
3446
3447
                foreach ($admin->getExtensions() as $extension) {
3448
                    $extension->validate($admin, $errorElement, $object);
3449
                }
3450
            },
3451
            'serializingWarning' => true,
3452
        ]));
3453
    }
3454
3455
    /**
3456
     * NEXT_MAJOR: Remove this function.
3457
     *
3458
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
3459
     *
3460
     * Predefine per page options.
3461
     */
3462
    protected function predefinePerPageOptions()
3463
    {
3464
        array_unshift($this->perPageOptions, $this->maxPerPage);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$perPageOptions has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$maxPerPage has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
3465
        $this->perPageOptions = array_unique($this->perPageOptions);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$perPageOptions has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
3466
        sort($this->perPageOptions);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$perPageOptions has been deprecated with message: since sonata-project/admin-bundle 3.67.

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...
3467
    }
3468
3469
    /**
3470
     * Return list routes with permissions name.
3471
     *
3472
     * @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...
3473
     */
3474
    protected function getAccess()
3475
    {
3476
        $access = array_merge([
3477
            'acl' => 'MASTER',
3478
            'export' => 'EXPORT',
3479
            'historyCompareRevisions' => 'EDIT',
3480
            'historyViewRevision' => 'EDIT',
3481
            'history' => 'EDIT',
3482
            'edit' => 'EDIT',
3483
            'show' => 'VIEW',
3484
            'create' => 'CREATE',
3485
            'delete' => 'DELETE',
3486
            'batchDelete' => 'DELETE',
3487
            'list' => 'LIST',
3488
        ], $this->getAccessMapping());
3489
3490
        foreach ($this->extensions as $extension) {
3491
            // NEXT_MAJOR: remove method check
3492
            if (method_exists($extension, 'getAccessMapping')) {
3493
                $access = array_merge($access, $extension->getAccessMapping($this));
3494
            }
3495
        }
3496
3497
        return $access;
3498
    }
3499
3500
    /**
3501
     * Configures a list of default filters.
3502
     */
3503
    protected function configureDefaultFilterValues(array &$filterValues)
3504
    {
3505
    }
3506
3507
    /**
3508
     * Configures a list of default sort values.
3509
     *
3510
     * Example:
3511
     *   $sortValues['_sort_by'] = 'foo'
3512
     *   $sortValues['_sort_order'] = 'DESC'
3513
     */
3514
    protected function configureDefaultSortValues(array &$sortValues)
0 ignored issues
show
Unused Code introduced by
The parameter $sortValues is not used and could be removed.

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

Loading history...
3515
    {
3516
    }
3517
3518
    /**
3519
     * Set the parent object, if any, to the provided object.
3520
     */
3521
    final protected function appendParentObject(object $object): void
3522
    {
3523
        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...
3524
            $parentAdmin = $this->getParent();
3525
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
3526
3527
            if (null !== $parentObject) {
3528
                $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3529
                $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3530
3531
                $value = $propertyAccessor->getValue($object, $propertyPath);
3532
3533
                if (\is_array($value) || $value instanceof \ArrayAccess) {
3534
                    $value[] = $parentObject;
3535
                    $propertyAccessor->setValue($object, $propertyPath, $value);
3536
                } else {
3537
                    $propertyAccessor->setValue($object, $propertyPath, $parentObject);
3538
                }
3539
            }
3540
        } elseif ($this->hasParentFieldDescription()) {
3541
            $parentAdmin = $this->getParentFieldDescription()->getAdmin();
3542
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
3543
3544
            if (null !== $parentObject) {
3545
                ObjectManipulator::setObject($object, $parentObject, $this->getParentFieldDescription());
0 ignored issues
show
Bug introduced by
It seems like $this->getParentFieldDescription() can be null; however, setObject() 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...
3546
            }
3547
        }
3548
    }
3549
3550
    /**
3551
     * Build all the related urls to the current admin.
3552
     */
3553
    private function buildRoutes(): void
3554
    {
3555
        if ($this->loaded['routes']) {
3556
            return;
3557
        }
3558
3559
        $this->loaded['routes'] = true;
3560
3561
        $this->routes = new RouteCollection(
3562
            $this->getBaseCodeRoute(),
3563
            $this->getBaseRouteName(),
3564
            $this->getBaseRoutePattern(),
3565
            $this->getBaseControllerName()
3566
        );
3567
3568
        $this->routeBuilder->build($this, $this->routes);
3569
3570
        $this->configureRoutes($this->routes);
3571
3572
        foreach ($this->getExtensions() as $extension) {
3573
            $extension->configureRoutes($this, $this->routes);
3574
        }
3575
    }
3576
}
3577
3578
class_exists(\Sonata\Form\Validator\ErrorElement::class);
3579