Completed
Pull Request — 3.x (#6263)
by
unknown
53:39
created

AbstractAdmin::setTranslator()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.9457
c 0
b 0
f 0
cc 6
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Admin;
15
16
use Doctrine\Common\Util\ClassUtils;
17
use Knp\Menu\FactoryInterface;
18
use Knp\Menu\ItemInterface;
19
use Sonata\AdminBundle\Builder\DatagridBuilderInterface;
20
use Sonata\AdminBundle\Builder\FormContractorInterface;
21
use Sonata\AdminBundle\Builder\ListBuilderInterface;
22
use Sonata\AdminBundle\Builder\RouteBuilderInterface;
23
use Sonata\AdminBundle\Builder\ShowBuilderInterface;
24
use Sonata\AdminBundle\Datagrid\DatagridInterface;
25
use Sonata\AdminBundle\Datagrid\DatagridMapper;
26
use Sonata\AdminBundle\Datagrid\ListMapper;
27
use Sonata\AdminBundle\Datagrid\Pager;
28
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
29
use Sonata\AdminBundle\Filter\Persister\FilterPersisterInterface;
30
use Sonata\AdminBundle\Form\FormMapper;
31
use Sonata\AdminBundle\Form\Type\ModelHiddenType;
32
use Sonata\AdminBundle\Manipulator\ObjectManipulator;
33
use Sonata\AdminBundle\Model\ModelManagerInterface;
34
use Sonata\AdminBundle\Object\Metadata;
35
use Sonata\AdminBundle\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\Form\FormInterface;
50
use Symfony\Component\HttpFoundation\Request;
51
use Symfony\Component\PropertyAccess\PropertyPath;
52
use Symfony\Component\Routing\Generator\UrlGeneratorInterface as RoutingUrlGeneratorInterface;
53
use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
54
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
55
use Symfony\Component\Translation\TranslatorInterface as DeprecatedTranslatorInterface;
56
use Symfony\Component\Validator\Mapping\GenericMetadata;
57
use Symfony\Component\Validator\Validator\ValidatorInterface;
58
use Symfony\Contracts\Translation\TranslatorInterface;
59
60
/**
61
 * @author Thomas Rabaix <[email protected]>
62
 */
63
abstract class AbstractAdmin implements AdminInterface, DomainObjectInterface, AdminTreeInterface
64
{
65
    public const CONTEXT_MENU = 'menu';
66
    public const CONTEXT_DASHBOARD = 'dashboard';
67
68
    public const CLASS_REGEX =
69
        '@
70
        (?:([A-Za-z0-9]*)\\\)?        # vendor name / app name
71
        (Bundle\\\)?                  # optional bundle directory
72
        ([A-Za-z0-9]+?)(?:Bundle)?\\\ # bundle name, with optional suffix
73
        (
74
            Entity|Document|Model|PHPCR|CouchDocument|Phpcr|
75
            Doctrine\\\Orm|Doctrine\\\Phpcr|Doctrine\\\MongoDB|Doctrine\\\CouchDB
76
        )\\\(.*)@x';
77
78
    public const MOSAIC_ICON_CLASS = 'fa fa-th-large fa-fw';
79
80
    /**
81
     * The list FieldDescription constructed from the configureListField method.
82
     *
83
     * @var FieldDescriptionInterface[]
84
     */
85
    protected $listFieldDescriptions = [];
86
87
    /**
88
     * The show FieldDescription constructed from the configureShowFields method.
89
     *
90
     * @var FieldDescriptionInterface[]
91
     */
92
    protected $showFieldDescriptions = [];
93
94
    /**
95
     * The list FieldDescription constructed from the configureFormField method.
96
     *
97
     * @var FieldDescriptionInterface[]
98
     */
99
    protected $formFieldDescriptions = [];
100
101
    /**
102
     * The filter FieldDescription constructed from the configureFilterField method.
103
     *
104
     * @var FieldDescriptionInterface[]
105
     */
106
    protected $filterFieldDescriptions = [];
107
108
    /**
109
     * NEXT_MAJOR: Remove this property.
110
     *
111
     * The number of result to display in the list.
112
     *
113
     * @deprecated since sonata-project/admin-bundle 3.67.
114
     *
115
     * @var int
116
     */
117
    protected $maxPerPage = 32;
118
119
    /**
120
     * The maximum number of page numbers to display in the list.
121
     *
122
     * @var int
123
     */
124
    protected $maxPageLinks = 25;
125
126
    /**
127
     * The base route name used to generate the routing information.
128
     *
129
     * @var string
130
     */
131
    protected $baseRouteName;
132
133
    /**
134
     * The base route pattern used to generate the routing information.
135
     *
136
     * @var string
137
     */
138
    protected $baseRoutePattern;
139
140
    /**
141
     * The base name controller used to generate the routing information.
142
     *
143
     * @var string
144
     */
145
    protected $baseControllerName;
146
147
    /**
148
     * The label class name  (used in the title/breadcrumb ...).
149
     *
150
     * @var string
151
     */
152
    protected $classnameLabel;
153
154
    /**
155
     * The translation domain to be used to translate messages.
156
     *
157
     * @var string
158
     */
159
    protected $translationDomain = 'messages';
160
161
    /**
162
     * Options to set to the form (ie, validation_groups).
163
     *
164
     * @var array
165
     */
166
    protected $formOptions = [];
167
168
    /**
169
     * NEXT_MAJOR: Remove this property.
170
     *
171
     * Default values to the datagrid.
172
     *
173
     * @deprecated since sonata-project/admin-bundle 3.67, use configureDefaultSortValues() instead.
174
     *
175
     * @var array
176
     */
177
    protected $datagridValues = [
178
        '_page' => 1,
179
        '_per_page' => 32,
180
    ];
181
182
    /**
183
     * NEXT_MAJOR: Remove this property.
184
     *
185
     * Predefined per page options.
186
     *
187
     * @deprecated since sonata-project/admin-bundle 3.67.
188
     *
189
     * @var array
190
     */
191
    protected $perPageOptions = [16, 32, 64, 128, 256];
192
193
    /**
194
     * Pager type.
195
     *
196
     * @var string
197
     */
198
    protected $pagerType = Pager::TYPE_DEFAULT;
199
200
    /**
201
     * The code related to the admin.
202
     *
203
     * @var string
204
     */
205
    protected $code;
206
207
    /**
208
     * The label.
209
     *
210
     * @var string
211
     */
212
    protected $label;
213
214
    /**
215
     * Whether or not to persist the filters in the session.
216
     *
217
     * NEXT_MAJOR: remove this property
218
     *
219
     * @var bool
220
     *
221
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
222
     */
223
    protected $persistFilters = false;
224
225
    /**
226
     * Array of routes related to this admin.
227
     *
228
     * @var RouteCollection
229
     */
230
    protected $routes;
231
232
    /**
233
     * The subject only set in edit/update/create mode.
234
     *
235
     * @var object|null
236
     */
237
    protected $subject;
238
239
    /**
240
     * Define a Collection of child admin, ie /admin/order/{id}/order-element/{childId}.
241
     *
242
     * @var array
243
     */
244
    protected $children = [];
245
246
    /**
247
     * Reference the parent admin.
248
     *
249
     * @var AdminInterface|null
250
     */
251
    protected $parent;
252
253
    /**
254
     * The base code route refer to the prefix used to generate the route name.
255
     *
256
     * NEXT_MAJOR: remove this attribute.
257
     *
258
     * @deprecated This attribute is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0
259
     *
260
     * @var string
261
     */
262
    protected $baseCodeRoute = '';
263
264
    /**
265
     * NEXT_MAJOR: should be default array and private.
266
     *
267
     * @var string|array
268
     */
269
    protected $parentAssociationMapping;
270
271
    /**
272
     * Reference the parent FieldDescription related to this admin
273
     * only set for FieldDescription which is associated to an Sub Admin instance.
274
     *
275
     * @var FieldDescriptionInterface
276
     */
277
    protected $parentFieldDescription;
278
279
    /**
280
     * If true then the current admin is part of the nested admin set (from the url).
281
     *
282
     * @var bool
283
     */
284
    protected $currentChild = false;
285
286
    /**
287
     * The uniqid is used to avoid clashing with 2 admin related to the code
288
     * ie: a Block linked to a Block.
289
     *
290
     * @var string
291
     */
292
    protected $uniqid;
293
294
    /**
295
     * The Entity or Document manager.
296
     *
297
     * @var ModelManagerInterface
298
     */
299
    protected $modelManager;
300
301
    /**
302
     * The current request object.
303
     *
304
     * @var Request|null
305
     */
306
    protected $request;
307
308
    /**
309
     * The translator component.
310
     *
311
     * NEXT_MAJOR: remove this property
312
     *
313
     * @var DeprecatedTranslatorInterface|TranslatorInterface
314
     *
315
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
316
     */
317
    protected $translator;
318
319
    /**
320
     * The related form contractor.
321
     *
322
     * @var FormContractorInterface
323
     */
324
    protected $formContractor;
325
326
    /**
327
     * The related list builder.
328
     *
329
     * @var ListBuilderInterface
330
     */
331
    protected $listBuilder;
332
333
    /**
334
     * The related view builder.
335
     *
336
     * @var ShowBuilderInterface
337
     */
338
    protected $showBuilder;
339
340
    /**
341
     * The related datagrid builder.
342
     *
343
     * @var DatagridBuilderInterface
344
     */
345
    protected $datagridBuilder;
346
347
    /**
348
     * @var RouteBuilderInterface
349
     */
350
    protected $routeBuilder;
351
352
    /**
353
     * The datagrid instance.
354
     *
355
     * @var DatagridInterface|null
356
     */
357
    protected $datagrid;
358
359
    /**
360
     * The router instance.
361
     *
362
     * @var RouteGeneratorInterface|null
363
     */
364
    protected $routeGenerator;
365
366
    /**
367
     * The generated breadcrumbs.
368
     *
369
     * NEXT_MAJOR : remove this property
370
     *
371
     * @var array
372
     */
373
    protected $breadcrumbs = [];
374
375
    /**
376
     * @var SecurityHandlerInterface
377
     */
378
    protected $securityHandler;
379
380
    /**
381
     * @var ValidatorInterface
382
     */
383
    protected $validator;
384
385
    /**
386
     * The configuration pool.
387
     *
388
     * @var Pool
389
     */
390
    protected $configurationPool;
391
392
    /**
393
     * @var ItemInterface
394
     */
395
    protected $menu;
396
397
    /**
398
     * @var FactoryInterface
399
     */
400
    protected $menuFactory;
401
402
    /**
403
     * @var array<string, bool>
404
     */
405
    protected $loaded = [
406
        'view_fields' => false, // NEXT_MAJOR: Remove this unused value.
407
        'view_groups' => false, // NEXT_MAJOR: Remove this unused value.
408
        'routes' => false,
409
        'tab_menu' => false,
410
        'show' => false,
411
        'list' => false,
412
        'form' => false,
413
        'datagrid' => false,
414
    ];
415
416
    /**
417
     * @var string[]
418
     */
419
    protected $formTheme = [];
420
421
    /**
422
     * @var string[]
423
     */
424
    protected $filterTheme = [];
425
426
    /**
427
     * @var array<string, string>
428
     *
429
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
430
     */
431
    protected $templates = [];
432
433
    /**
434
     * @var AdminExtensionInterface[]
435
     */
436
    protected $extensions = [];
437
438
    /**
439
     * @var LabelTranslatorStrategyInterface
440
     */
441
    protected $labelTranslatorStrategy;
442
443
    /**
444
     * Setting to true will enable preview mode for
445
     * the entity and show a preview button in the
446
     * edit/create forms.
447
     *
448
     * @var bool
449
     */
450
    protected $supportsPreviewMode = false;
451
452
    /**
453
     * Roles and permissions per role.
454
     *
455
     * @var array 'role' => ['permission', 'permission']
456
     */
457
    protected $securityInformation = [];
458
459
    protected $cacheIsGranted = [];
460
461
    /**
462
     * Action list for the search result.
463
     *
464
     * @var string[]
465
     */
466
    protected $searchResultActions = ['edit', 'show'];
467
468
    protected $listModes = [
469
        'list' => [
470
            'class' => 'fa fa-list fa-fw',
471
        ],
472
        'mosaic' => [
473
            'class' => self::MOSAIC_ICON_CLASS,
474
        ],
475
    ];
476
477
    /**
478
     * The Access mapping.
479
     *
480
     * @var array [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
481
     */
482
    protected $accessMapping = [];
483
484
    /**
485
     * @var MutableTemplateRegistryInterface
486
     */
487
    private $templateRegistry;
488
489
    /**
490
     * The class name managed by the admin class.
491
     *
492
     * @var string
493
     */
494
    private $class;
495
496
    /**
497
     * The subclasses supported by the admin class.
498
     *
499
     * @var array<string, string>
500
     */
501
    private $subClasses = [];
502
503
    /**
504
     * The list collection.
505
     *
506
     * @var FieldDescriptionCollection|null
507
     */
508
    private $list;
509
510
    /**
511
     * @var FieldDescriptionCollection|null
512
     */
513
    private $show;
514
515
    /**
516
     * @var FormInterface|null
517
     */
518
    private $form;
519
520
    /**
521
     * The cached base route name.
522
     *
523
     * @var string
524
     */
525
    private $cachedBaseRouteName;
526
527
    /**
528
     * The cached base route pattern.
529
     *
530
     * @var string
531
     */
532
    private $cachedBaseRoutePattern;
533
534
    /**
535
     * The form group disposition.
536
     *
537
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
538
     * hold boolean values.
539
     *
540
     * @var array|bool
541
     */
542
    private $formGroups = false;
543
544
    /**
545
     * The form tabs disposition.
546
     *
547
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
548
     * hold boolean values.
549
     *
550
     * @var array|bool
551
     */
552
    private $formTabs = false;
553
554
    /**
555
     * The view group disposition.
556
     *
557
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
558
     * hold boolean values.
559
     *
560
     * @var array|bool
561
     */
562
    private $showGroups = false;
563
564
    /**
565
     * The view tab disposition.
566
     *
567
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
568
     * hold boolean values.
569
     *
570
     * @var array|bool
571
     */
572
    private $showTabs = false;
573
574
    /**
575
     * The manager type to use for the admin.
576
     *
577
     * @var string
578
     */
579
    private $managerType;
580
581
    /**
582
     * The breadcrumbsBuilder component.
583
     *
584
     * @var BreadcrumbsBuilderInterface
585
     */
586
    private $breadcrumbsBuilder;
587
588
    /**
589
     * Component responsible for persisting filters.
590
     *
591
     * @var FilterPersisterInterface|null
592
     */
593
    private $filterPersister;
594
595
    /**
596
     * @param string      $code
597
     * @param string      $class
598
     * @param string|null $baseControllerName
599
     */
600
    public function __construct($code, $class, $baseControllerName = null)
601
    {
602
        if (!\is_string($code)) {
603
            @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...
604
                'Passing other type than string as argument 1 for method %s() is deprecated since'
605
                .' sonata-project/admin-bundle 3.65. It will accept only string in version 4.0.',
606
                __METHOD__
607
            ), E_USER_DEPRECATED);
608
        }
609
        $this->code = $code;
610
        if (!\is_string($class)) {
611
            @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...
612
                'Passing other type than string as argument 2 for method %s() is deprecated since'
613
                .' sonata-project/admin-bundle 3.65. It will accept only string in version 4.0.',
614
                __METHOD__
615
            ), E_USER_DEPRECATED);
616
        }
617
        $this->class = $class;
618
        if (null !== $baseControllerName && !\is_string($baseControllerName)) {
619
            @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...
620
                'Passing other type than string or null as argument 3 for method %s() is deprecated since'
621
                .' sonata-project/admin-bundle 3.65. It will accept only string and null in version 4.0.',
622
                __METHOD__
623
            ), E_USER_DEPRECATED);
624
        }
625
        $this->baseControllerName = $baseControllerName;
626
627
        // NEXT_MAJOR: Remove this line.
628
        $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...
629
630
        // NEXT_MAJOR: Remove this line.
631
        $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...
632
    }
633
634
    /**
635
     * {@inheritdoc}
636
     */
637
    public function getExportFormats()
638
    {
639
        return [
640
            'json', 'xml', 'csv', 'xls',
641
        ];
642
    }
643
644
    /**
645
     * @return array
646
     */
647
    public function getExportFields()
648
    {
649
        $fields = $this->getModelManager()->getExportFields($this->getClass());
650
651
        foreach ($this->getExtensions() as $extension) {
652
            if (method_exists($extension, 'configureExportFields')) {
653
                $fields = $extension->configureExportFields($this, $fields);
654
            }
655
        }
656
657
        return $fields;
658
    }
659
660
    public function getDataSourceIterator()
661
    {
662
        $datagrid = $this->getDatagrid();
663
        $datagrid->buildPager();
664
665
        $fields = [];
666
667
        foreach ($this->getExportFields() as $key => $field) {
668
            $label = $this->getTranslationLabel($field, 'export', 'label');
669
670
            // NEXT_MAJOR: We have to find another way to have a translated label or stop deprecating the translator.
671
            $transLabel = $this->trans($label);
672
673
            // NEXT_MAJOR: Remove the following code in favor of the commented one.
674
            // If a key is provided we use it otherwise we use the generated label.
675
            // $fieldKey = \is_string($key) ? $key : $transLabel;
676
            // $fields[$fieldKey] = $field;
677
            if ($transLabel === $label) {
678
                $fields[$key] = $field;
679
            } else {
680
                $fields[$transLabel] = $field;
681
            }
682
        }
683
684
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
0 ignored issues
show
Bug introduced by
It seems like $datagrid defined by $this->getDatagrid() on line 662 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...
685
    }
686
687
    public function validate(ErrorElement $errorElement, $object)
688
    {
689
    }
690
691
    /**
692
     * define custom variable.
693
     */
694
    public function initialize()
695
    {
696
        if (!$this->classnameLabel) {
697
            /* NEXT_MAJOR: remove cast to string, null is not supposed to be
698
            supported but was documented as such */
699
            $this->classnameLabel = substr(
700
                (string) $this->getClass(),
701
                strrpos((string) $this->getClass(), '\\') + 1
702
            );
703
        }
704
705
        // NEXT_MAJOR: Remove this line.
706
        $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...
707
708
        $this->configure();
709
    }
710
711
    public function configure()
712
    {
713
    }
714
715
    public function update($object)
716
    {
717
        $this->preUpdate($object);
718
        foreach ($this->extensions as $extension) {
719
            $extension->preUpdate($this, $object);
720
        }
721
722
        $result = $this->getModelManager()->update($object);
723
        // BC compatibility
724
        if (null !== $result) {
725
            $object = $result;
726
        }
727
728
        $this->postUpdate($object);
729
        foreach ($this->extensions as $extension) {
730
            $extension->postUpdate($this, $object);
731
        }
732
733
        return $object;
734
    }
735
736
    public function create($object)
737
    {
738
        $this->prePersist($object);
739
        foreach ($this->extensions as $extension) {
740
            $extension->prePersist($this, $object);
741
        }
742
743
        $result = $this->getModelManager()->create($object);
744
        // BC compatibility
745
        if (null !== $result) {
746
            $object = $result;
747
        }
748
749
        $this->postPersist($object);
750
        foreach ($this->extensions as $extension) {
751
            $extension->postPersist($this, $object);
752
        }
753
754
        $this->createObjectSecurity($object);
755
756
        return $object;
757
    }
758
759
    public function delete($object)
760
    {
761
        $this->preRemove($object);
762
        foreach ($this->extensions as $extension) {
763
            $extension->preRemove($this, $object);
764
        }
765
766
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
767
        $this->getModelManager()->delete($object);
768
769
        $this->postRemove($object);
770
        foreach ($this->extensions as $extension) {
771
            $extension->postRemove($this, $object);
772
        }
773
    }
774
775
    /**
776
     * @param object $object
777
     */
778
    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...
779
    {
780
    }
781
782
    public function preUpdate($object)
783
    {
784
    }
785
786
    public function postUpdate($object)
787
    {
788
    }
789
790
    public function prePersist($object)
791
    {
792
    }
793
794
    public function postPersist($object)
795
    {
796
    }
797
798
    public function preRemove($object)
799
    {
800
    }
801
802
    public function postRemove($object)
803
    {
804
    }
805
806
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
807
    {
808
    }
809
810
    public function getFilterParameters()
811
    {
812
        $parameters = [];
813
814
        // build the values array
815
        if ($this->hasRequest()) {
816
            $filters = $this->request->query->get('filter', []);
817
            if (isset($filters['_page'])) {
818
                $filters['_page'] = (int) $filters['_page'];
819
            }
820
            if (isset($filters['_per_page'])) {
821
                $filters['_per_page'] = (int) $filters['_per_page'];
822
            }
823
824
            // if filter persistence is configured
825
            // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
826
            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...
827
                // if reset filters is asked, remove from storage
828
                if ('reset' === $this->request->query->get('filters')) {
829
                    $this->filterPersister->reset($this->getCode());
830
                }
831
832
                // if no filters, fetch from storage
833
                // otherwise save to storage
834
                if (empty($filters)) {
835
                    $filters = $this->filterPersister->get($this->getCode());
836
                } else {
837
                    $this->filterPersister->set($this->getCode(), $filters);
838
                }
839
            }
840
841
            $parameters = array_merge(
842
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
843
                $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...
844
                $this->getDefaultSortValues(),
845
                $this->getDefaultFilterValues(),
846
                $filters
847
            );
848
849
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
850
                $parameters['_per_page'] = $this->getMaxPerPage();
851
            }
852
853
            // always force the parent value
854
            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...
855
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
856
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
857
            }
858
        }
859
860
        return $parameters;
861
    }
862
863
    /**
864
     * NEXT_MAJOR: Change the visibility to protected (similar to buildShow, buildForm, ...).
865
     */
866
    public function buildDatagrid()
867
    {
868
        if ($this->loaded['datagrid']) {
869
            return;
870
        }
871
872
        $this->loaded['datagrid'] = true;
873
874
        $filterParameters = $this->getFilterParameters();
875
876
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
877
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
878
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
879
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
880
            } else {
881
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
882
                    $this->getClass(),
883
                    $filterParameters['_sort_by'],
884
                    []
885
                );
886
887
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
888
            }
889
        }
890
891
        // initialize the datagrid
892
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
893
894
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
895
896
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
897
898
        // build the datagrid filter
899
        $this->configureDatagridFilters($mapper);
900
901
        // ok, try to limit to add parent filter
902
        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...
903
            $mapper->add($this->getParentAssociationMapping(), null, [
904
                'show_filter' => false,
905
                'label' => false,
906
                'field_type' => ModelHiddenType::class,
907
                'field_options' => [
908
                    'model_manager' => $this->getModelManager(),
909
                ],
910
                'operator_type' => HiddenType::class,
911
            ], null, null, [
912
                'admin_code' => $this->getParent()->getCode(),
913
            ]);
914
        }
915
916
        foreach ($this->getExtensions() as $extension) {
917
            $extension->configureDatagridFilters($mapper);
918
        }
919
    }
920
921
    /**
922
     * Returns the name of the parent related field, so the field can be use to set the default
923
     * value (ie the parent object) or to filter the object.
924
     *
925
     * @throws \InvalidArgumentException
926
     *
927
     * @return string|null
928
     */
929
    public function getParentAssociationMapping()
930
    {
931
        // NEXT_MAJOR: remove array check
932
        if (\is_array($this->parentAssociationMapping) && $this->isChild()) {
933
            $parent = $this->getParent()->getCode();
934
935
            if (\array_key_exists($parent, $this->parentAssociationMapping)) {
936
                return $this->parentAssociationMapping[$parent];
937
            }
938
939
            throw new \InvalidArgumentException(sprintf(
940
                'There\'s no association between %s and %s.',
941
                $this->getCode(),
942
                $this->getParent()->getCode()
943
            ));
944
        }
945
946
        // NEXT_MAJOR: remove this line
947
        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 947 which is incompatible with the return type documented by Sonata\AdminBundle\Admin...arentAssociationMapping of type string|null.
Loading history...
948
    }
949
950
    /**
951
     * @param string $code
952
     * @param string $value
953
     */
954
    final public function addParentAssociationMapping($code, $value)
955
    {
956
        $this->parentAssociationMapping[$code] = $value;
957
    }
958
959
    /**
960
     * Returns the baseRoutePattern used to generate the routing information.
961
     *
962
     * @throws \RuntimeException
963
     *
964
     * @return string the baseRoutePattern used to generate the routing information
965
     */
966
    public function getBaseRoutePattern()
967
    {
968
        if (null !== $this->cachedBaseRoutePattern) {
969
            return $this->cachedBaseRoutePattern;
970
        }
971
972
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
973
            $baseRoutePattern = $this->baseRoutePattern;
974
            if (!$this->baseRoutePattern) {
975
                preg_match(self::CLASS_REGEX, $this->class, $matches);
976
977
                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...
978
                    throw new \RuntimeException(sprintf(
979
                        'Please define a default `baseRoutePattern` value for the admin class `%s`',
980
                        static::class
981
                    ));
982
                }
983
                $baseRoutePattern = $this->urlize($matches[5], '-');
984
            }
985
986
            $this->cachedBaseRoutePattern = sprintf(
987
                '%s/%s/%s',
988
                $this->getParent()->getBaseRoutePattern(),
989
                $this->getParent()->getRouterIdParameter(),
990
                $baseRoutePattern
991
            );
992
        } elseif ($this->baseRoutePattern) {
993
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
994
        } else {
995
            preg_match(self::CLASS_REGEX, $this->class, $matches);
996
997
            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...
998
                throw new \RuntimeException(sprintf(
999
                    'Please define a default `baseRoutePattern` value for the admin class `%s`',
1000
                    static::class
1001
                ));
1002
            }
1003
1004
            $this->cachedBaseRoutePattern = sprintf(
1005
                '/%s%s/%s',
1006
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
1007
                $this->urlize($matches[3], '-'),
1008
                $this->urlize($matches[5], '-')
1009
            );
1010
        }
1011
1012
        return $this->cachedBaseRoutePattern;
1013
    }
1014
1015
    /**
1016
     * Returns the baseRouteName used to generate the routing information.
1017
     *
1018
     * @throws \RuntimeException
1019
     *
1020
     * @return string the baseRouteName used to generate the routing information
1021
     */
1022
    public function getBaseRouteName()
1023
    {
1024
        if (null !== $this->cachedBaseRouteName) {
1025
            return $this->cachedBaseRouteName;
1026
        }
1027
1028
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
1029
            $baseRouteName = $this->baseRouteName;
1030
            if (!$this->baseRouteName) {
1031
                preg_match(self::CLASS_REGEX, $this->class, $matches);
1032
1033
                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...
1034
                    throw new \RuntimeException(sprintf(
1035
                        'Cannot automatically determine base route name,'
1036
                        .' please define a default `baseRouteName` value for the admin class `%s`',
1037
                        static::class
1038
                    ));
1039
                }
1040
                $baseRouteName = $this->urlize($matches[5]);
1041
            }
1042
1043
            $this->cachedBaseRouteName = sprintf(
1044
                '%s_%s',
1045
                $this->getParent()->getBaseRouteName(),
1046
                $baseRouteName
1047
            );
1048
        } elseif ($this->baseRouteName) {
1049
            $this->cachedBaseRouteName = $this->baseRouteName;
1050
        } else {
1051
            preg_match(self::CLASS_REGEX, $this->class, $matches);
1052
1053
            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...
1054
                throw new \RuntimeException(sprintf(
1055
                    'Cannot automatically determine base route name,'
1056
                    .' please define a default `baseRouteName` value for the admin class `%s`',
1057
                    static::class
1058
                ));
1059
            }
1060
1061
            $this->cachedBaseRouteName = sprintf(
1062
                'admin_%s%s_%s',
1063
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
1064
                $this->urlize($matches[3]),
1065
                $this->urlize($matches[5])
1066
            );
1067
        }
1068
1069
        return $this->cachedBaseRouteName;
1070
    }
1071
1072
    /**
1073
     * urlize the given word.
1074
     *
1075
     * @param string $word
1076
     * @param string $sep  the separator
1077
     *
1078
     * @return string
1079
     */
1080
    public function urlize($word, $sep = '_')
1081
    {
1082
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
1083
    }
1084
1085
    public function getClass()
1086
    {
1087
        if ($this->hasActiveSubClass()) {
1088
            if ($this->hasParentFieldDescription()) {
1089
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
1090
            }
1091
1092
            $subClass = $this->getRequest()->query->get('subclass');
1093
1094
            if (!$this->hasSubClass($subClass)) {
1095
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
1096
            }
1097
1098
            return $this->getSubClass($subClass);
1099
        }
1100
1101
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
1102
        if ($this->subject && \is_object($this->subject)) {
1103
            return ClassUtils::getClass($this->subject);
1104
        }
1105
1106
        return $this->class;
1107
    }
1108
1109
    public function getSubClasses()
1110
    {
1111
        return $this->subClasses;
1112
    }
1113
1114
    /**
1115
     * NEXT_MAJOR: remove this method.
1116
     */
1117
    public function addSubClass($subClass)
1118
    {
1119
        @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...
1120
            'Method "%s" is deprecated since sonata-project/admin-bundle 3.30 and will be removed in 4.0.',
1121
            __METHOD__
1122
        ), E_USER_DEPRECATED);
1123
1124
        if (!\in_array($subClass, $this->subClasses, true)) {
1125
            $this->subClasses[] = $subClass;
1126
        }
1127
    }
1128
1129
    public function setSubClasses(array $subClasses)
1130
    {
1131
        $this->subClasses = $subClasses;
1132
    }
1133
1134
    public function hasSubClass($name)
1135
    {
1136
        return isset($this->subClasses[$name]);
1137
    }
1138
1139
    public function hasActiveSubClass()
1140
    {
1141
        if (\count($this->subClasses) > 0 && $this->request) {
1142
            return null !== $this->getRequest()->query->get('subclass');
1143
        }
1144
1145
        return false;
1146
    }
1147
1148
    public function getActiveSubClass()
1149
    {
1150
        if (!$this->hasActiveSubClass()) {
1151
            @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...
1152
                'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52'
1153
                .' and will throw an exception in 4.0.'
1154
                .' Use %s::hasActiveSubClass() to know if there is an active subclass.',
1155
                __METHOD__,
1156
                __CLASS__
1157
            ), E_USER_DEPRECATED);
1158
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1159
            // throw new \LogicException(sprintf(
1160
            //    'Admin "%s" has no active subclass.',
1161
            //    static::class
1162
            // ));
1163
1164
            return null;
1165
        }
1166
1167
        return $this->getSubClass($this->getActiveSubclassCode());
1168
    }
1169
1170
    public function getActiveSubclassCode()
1171
    {
1172
        if (!$this->hasActiveSubClass()) {
1173
            @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...
1174
                'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52'
1175
                .' and will throw an exception in 4.0.'
1176
                .' Use %s::hasActiveSubClass() to know if there is an active subclass.',
1177
                __METHOD__,
1178
                __CLASS__
1179
            ), E_USER_DEPRECATED);
1180
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1181
            // throw new \LogicException(sprintf(
1182
            //    'Admin "%s" has no active subclass.',
1183
            //    static::class
1184
            // ));
1185
1186
            return null;
1187
        }
1188
1189
        $subClass = $this->getRequest()->query->get('subclass');
1190
1191
        if (!$this->hasSubClass($subClass)) {
1192
            @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...
1193
                'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52'
1194
                .' and will throw an exception in 4.0.'
1195
                .' Use %s::hasActiveSubClass() to know if there is an active subclass.',
1196
                __METHOD__,
1197
                __CLASS__
1198
            ), E_USER_DEPRECATED);
1199
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1200
            // throw new \LogicException(sprintf(
1201
            //    'Admin "%s" has no active subclass.',
1202
            //    static::class
1203
            // ));
1204
1205
            return null;
1206
        }
1207
1208
        return $subClass;
1209
    }
1210
1211
    public function getBatchActions()
1212
    {
1213
        $actions = [];
1214
1215
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1216
            $actions['delete'] = [
1217
                'label' => 'action_delete',
1218
                'translation_domain' => 'SonataAdminBundle',
1219
                'ask_confirmation' => true, // by default always true
1220
            ];
1221
        }
1222
1223
        $actions = $this->configureBatchActions($actions);
1224
1225
        foreach ($this->getExtensions() as $extension) {
1226
            // NEXT_MAJOR: remove method check
1227
            if (method_exists($extension, 'configureBatchActions')) {
1228
                $actions = $extension->configureBatchActions($this, $actions);
1229
            }
1230
        }
1231
1232
        foreach ($actions  as $name => &$action) {
1233
            if (!\array_key_exists('label', $action)) {
1234
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1235
            }
1236
1237
            if (!\array_key_exists('translation_domain', $action)) {
1238
                $action['translation_domain'] = $this->getTranslationDomain();
1239
            }
1240
        }
1241
1242
        return $actions;
1243
    }
1244
1245
    public function getRoutes()
1246
    {
1247
        $this->buildRoutes();
1248
1249
        return $this->routes;
1250
    }
1251
1252
    public function getRouterIdParameter()
1253
    {
1254
        return sprintf('{%s}', $this->getIdParameter());
1255
    }
1256
1257
    public function getIdParameter()
1258
    {
1259
        $parameter = 'id';
1260
1261
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1262
            $parameter = sprintf('child%s', ucfirst($parameter));
1263
        }
1264
1265
        return $parameter;
1266
    }
1267
1268
    public function hasRoute($name)
1269
    {
1270
        if (!$this->routeGenerator) {
1271
            throw new \RuntimeException('RouteGenerator cannot be null');
1272
        }
1273
1274
        return $this->routeGenerator->hasAdminRoute($this, $name);
1275
    }
1276
1277
    /**
1278
     * @param string      $name
1279
     * @param string|null $adminCode
1280
     *
1281
     * @return bool
1282
     */
1283
    public function isCurrentRoute($name, $adminCode = null)
1284
    {
1285
        if (!$this->hasRequest()) {
1286
            return false;
1287
        }
1288
1289
        $request = $this->getRequest();
1290
        $route = $request->get('_route');
1291
1292
        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...
1293
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1294
        } else {
1295
            $admin = $this;
1296
        }
1297
1298
        if (!$admin) {
1299
            return false;
1300
        }
1301
1302
        return sprintf('%s_%s', $admin->getBaseRouteName(), $name) === $route;
1303
    }
1304
1305
    public function generateObjectUrl($name, $object, array $parameters = [], $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1306
    {
1307
        $parameters['id'] = $this->getUrlSafeIdentifier($object);
1308
1309
        return $this->generateUrl($name, $parameters, $referenceType);
1310
    }
1311
1312
    public function generateUrl($name, array $parameters = [], $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1313
    {
1314
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $referenceType);
1315
    }
1316
1317
    public function generateMenuUrl($name, array $parameters = [], $referenceType = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1318
    {
1319
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $referenceType);
1320
    }
1321
1322
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry)
1323
    {
1324
        $this->templateRegistry = $templateRegistry;
1325
    }
1326
1327
    /**
1328
     * @param array<string, string> $templates
1329
     */
1330
    public function setTemplates(array $templates)
1331
    {
1332
        // NEXT_MAJOR: Remove this line
1333
        $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...
1334
1335
        $this->getTemplateRegistry()->setTemplates($templates);
1336
    }
1337
1338
    /**
1339
     * @param string $name
1340
     * @param string $template
1341
     */
1342
    public function setTemplate($name, $template)
1343
    {
1344
        // NEXT_MAJOR: Remove this line
1345
        $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...
1346
1347
        $this->getTemplateRegistry()->setTemplate($name, $template);
1348
    }
1349
1350
    /**
1351
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1352
     *
1353
     * @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...
1354
     */
1355
    public function getTemplates()
1356
    {
1357
        return $this->getTemplateRegistry()->getTemplates();
1358
    }
1359
1360
    /**
1361
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1362
     *
1363
     * @param string $name
1364
     *
1365
     * @return string|null
1366
     */
1367
    public function getTemplate($name)
1368
    {
1369
        return $this->getTemplateRegistry()->getTemplate($name);
1370
    }
1371
1372
    public function getNewInstance()
1373
    {
1374
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1375
1376
        $this->appendParentObject($object);
1377
1378
        foreach ($this->getExtensions() as $extension) {
1379
            $extension->alterNewInstance($this, $object);
1380
        }
1381
1382
        return $object;
1383
    }
1384
1385
    public function getFormBuilder()
1386
    {
1387
        $this->formOptions['data_class'] = $this->getClass();
1388
1389
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1390
            $this->getUniqid(),
1391
            $this->formOptions
1392
        );
1393
1394
        $this->defineFormBuilder($formBuilder);
1395
1396
        return $formBuilder;
1397
    }
1398
1399
    /**
1400
     * This method is being called by the main admin class and the child class,
1401
     * the getFormBuilder is only call by the main admin class.
1402
     */
1403
    public function defineFormBuilder(FormBuilderInterface $formBuilder)
1404
    {
1405
        if (!$this->hasSubject()) {
1406
            @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...
1407
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.65'
1408
                .' and will throw an exception in 4.0. Use %s::setSubject() to set the subject.',
1409
                __METHOD__,
1410
                __CLASS__
1411
            ), E_USER_DEPRECATED);
1412
            // NEXT_MAJOR : remove the previous `trigger_error()` call and uncomment the following exception
1413
            // throw new \LogicException(sprintf(
1414
            //    'Admin "%s" has no subject.',
1415
            //    static::class
1416
            // ));
1417
        }
1418
1419
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1420
1421
        $this->configureFormFields($mapper);
1422
1423
        foreach ($this->getExtensions() as $extension) {
1424
            $extension->configureFormFields($mapper);
1425
        }
1426
1427
        $this->attachInlineValidator();
1428
    }
1429
1430
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
1431
    {
1432
        $pool = $this->getConfigurationPool();
1433
1434
        $adminCode = $fieldDescription->getOption('admin_code');
1435
1436
        if (null !== $adminCode) {
1437
            if (!$pool->hasAdminByAdminCode($adminCode)) {
1438
                return;
1439
            }
1440
1441
            $admin = $pool->getAdminByAdminCode($adminCode);
1442
        } else {
1443
            // NEXT_MAJOR: Remove the check and use `getTargetModel`.
1444
            if (method_exists($fieldDescription, 'getTargetModel')) {
1445
                $targetModel = $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...
1446
            } else {
1447
                $targetModel = $fieldDescription->getTargetEntity();
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...face::getTargetEntity() has been deprecated with message: since sonata-project/admin-bundle 3.69. Use `getTargetModel()` 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...
1448
            }
1449
1450
            if (!$pool->hasAdminByClass($targetModel)) {
1451
                return;
1452
            }
1453
1454
            $admin = $pool->getAdminByClass($targetModel);
1455
        }
1456
1457
        if ($this->hasRequest()) {
1458
            $admin->setRequest($this->getRequest());
1459
        }
1460
1461
        $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...
1462
    }
1463
1464
    public function getObject($id)
1465
    {
1466
        $object = $this->getModelManager()->find($this->getClass(), $id);
1467
        foreach ($this->getExtensions() as $extension) {
1468
            $extension->alterObject($this, $object);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getModelManager()...$this->getClass(), $id) on line 1466 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...
1469
        }
1470
1471
        return $object;
1472
    }
1473
1474
    public function getForm()
1475
    {
1476
        $this->buildForm();
1477
1478
        return $this->form;
1479
    }
1480
1481
    public function getList()
1482
    {
1483
        $this->buildList();
1484
1485
        return $this->list;
1486
    }
1487
1488
    /**
1489
     * @final since sonata-project/admin-bundle 3.63.0
1490
     */
1491
    public function createQuery($context = 'list')
1492
    {
1493
        if (\func_num_args() > 0) {
1494
            @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...
1495
                'The $context argument of %s is deprecated since 3.3, to be removed in 4.0.',
1496
                __METHOD__
1497
            ), E_USER_DEPRECATED);
1498
        }
1499
1500
        $query = $this->getModelManager()->createQuery($this->getClass());
1501
1502
        $query = $this->configureQuery($query);
1503
        foreach ($this->extensions as $extension) {
1504
            $extension->configureQuery($this, $query, $context);
1505
        }
1506
1507
        return $query;
1508
    }
1509
1510
    public function getDatagrid()
1511
    {
1512
        $this->buildDatagrid();
1513
1514
        return $this->datagrid;
1515
    }
1516
1517
    public function buildTabMenu($action, ?AdminInterface $childAdmin = null)
1518
    {
1519
        if ($this->loaded['tab_menu']) {
1520
            return $this->menu;
1521
        }
1522
1523
        $this->loaded['tab_menu'] = true;
1524
1525
        $menu = $this->menuFactory->createItem('root');
1526
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1527
        $menu->setExtra('translation_domain', $this->translationDomain);
1528
1529
        // Prevents BC break with KnpMenuBundle v1.x
1530
        if (method_exists($menu, 'setCurrentUri')) {
1531
            $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...
1532
        }
1533
1534
        $this->configureTabMenu($menu, $action, $childAdmin);
1535
1536
        foreach ($this->getExtensions() as $extension) {
1537
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1538
        }
1539
1540
        $this->menu = $menu;
1541
1542
        return $this->menu;
1543
    }
1544
1545
    public function buildSideMenu($action, ?AdminInterface $childAdmin = null)
1546
    {
1547
        return $this->buildTabMenu($action, $childAdmin);
1548
    }
1549
1550
    /**
1551
     * @param string $action
1552
     *
1553
     * @return ItemInterface
1554
     */
1555
    public function getSideMenu($action, ?AdminInterface $childAdmin = null)
1556
    {
1557
        if ($this->isChild()) {
1558
            return $this->getParent()->getSideMenu($action, $this);
1559
        }
1560
1561
        $this->buildSideMenu($action, $childAdmin);
1562
1563
        return $this->menu;
1564
    }
1565
1566
    /**
1567
     * Returns the root code.
1568
     *
1569
     * @return string the root code
1570
     */
1571
    public function getRootCode()
1572
    {
1573
        return $this->getRoot()->getCode();
1574
    }
1575
1576
    /**
1577
     * Returns the master admin.
1578
     *
1579
     * @return AdminInterface the root admin class
1580
     */
1581
    public function getRoot()
1582
    {
1583
        if (!$this->hasParentFieldDescription()) {
1584
            return $this;
1585
        }
1586
1587
        return $this->getParentFieldDescription()->getAdmin()->getRoot();
1588
    }
1589
1590
    public function setBaseControllerName($baseControllerName)
1591
    {
1592
        $this->baseControllerName = $baseControllerName;
1593
    }
1594
1595
    public function getBaseControllerName()
1596
    {
1597
        return $this->baseControllerName;
1598
    }
1599
1600
    /**
1601
     * @param string $label
1602
     */
1603
    public function setLabel($label)
1604
    {
1605
        $this->label = $label;
1606
    }
1607
1608
    public function getLabel()
1609
    {
1610
        return $this->label;
1611
    }
1612
1613
    /**
1614
     * @param bool $persist
1615
     *
1616
     * NEXT_MAJOR: remove this method
1617
     *
1618
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
1619
     */
1620
    public function setPersistFilters($persist)
1621
    {
1622
        @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...
1623
            'The %s method is deprecated since version 3.34 and will be removed in 4.0.',
1624
            __METHOD__
1625
        ), E_USER_DEPRECATED);
1626
1627
        $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...
1628
    }
1629
1630
    public function setFilterPersister(?FilterPersisterInterface $filterPersister = null)
1631
    {
1632
        $this->filterPersister = $filterPersister;
1633
        // NEXT_MAJOR remove the deprecated property will be removed. Needed for persisted filter condition.
1634
        $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...
1635
    }
1636
1637
    /**
1638
     * NEXT_MAJOR: Remove this method.
1639
     *
1640
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
1641
     *
1642
     * @param int $maxPerPage
1643
     */
1644
    public function setMaxPerPage($maxPerPage)
1645
    {
1646
        @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...
1647
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
1648
            __METHOD__
1649
        ), E_USER_DEPRECATED);
1650
1651
        $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...
1652
    }
1653
1654
    /**
1655
     * @return int
1656
     */
1657
    public function getMaxPerPage()
1658
    {
1659
        // NEXT_MAJOR: Remove this line and uncomment the following.
1660
        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...
1661
        // $sortValues = $this->getModelManager()->getDefaultSortValues($this->class);
1662
1663
        // return $sortValues['_per_page'] ?? 25;
1664
    }
1665
1666
    /**
1667
     * @param int $maxPageLinks
1668
     */
1669
    public function setMaxPageLinks($maxPageLinks)
1670
    {
1671
        $this->maxPageLinks = $maxPageLinks;
1672
    }
1673
1674
    /**
1675
     * @return int
1676
     */
1677
    public function getMaxPageLinks()
1678
    {
1679
        return $this->maxPageLinks;
1680
    }
1681
1682
    public function getFormGroups()
1683
    {
1684
        if (!\is_array($this->formGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1685
            @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...
1686
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65.'
1687
                .' It will return only array in version 4.0.',
1688
                __METHOD__
1689
            ), E_USER_DEPRECATED);
1690
        }
1691
1692
        return $this->formGroups;
1693
    }
1694
1695
    public function setFormGroups(array $formGroups)
1696
    {
1697
        $this->formGroups = $formGroups;
1698
    }
1699
1700
    public function removeFieldFromFormGroup($key)
1701
    {
1702
        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...
1703
            unset($this->formGroups[$name]['fields'][$key]);
1704
1705
            if (empty($this->formGroups[$name]['fields'])) {
1706
                unset($this->formGroups[$name]);
1707
            }
1708
        }
1709
    }
1710
1711
    /**
1712
     * @param string $group
1713
     */
1714
    public function reorderFormGroup($group, array $keys)
1715
    {
1716
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1717
        $formGroups = $this->getFormGroups('sonata_deprecation_mute');
1718
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1719
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups('sonata_deprecation_mute') on line 1717 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...
1720
    }
1721
1722
    public function getFormTabs()
1723
    {
1724
        if (!\is_array($this->formTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1725
            @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...
1726
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65.'
1727
                .' It will return only array in version 4.0.',
1728
                __METHOD__
1729
            ), E_USER_DEPRECATED);
1730
        }
1731
1732
        return $this->formTabs;
1733
    }
1734
1735
    public function setFormTabs(array $formTabs)
1736
    {
1737
        $this->formTabs = $formTabs;
1738
    }
1739
1740
    public function getShowTabs()
1741
    {
1742
        if (!\is_array($this->showTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1743
            @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...
1744
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65.'
1745
                .' It will return only array in version 4.0.',
1746
                __METHOD__
1747
            ), E_USER_DEPRECATED);
1748
        }
1749
1750
        return $this->showTabs;
1751
    }
1752
1753
    public function setShowTabs(array $showTabs)
1754
    {
1755
        $this->showTabs = $showTabs;
1756
    }
1757
1758
    public function getShowGroups()
1759
    {
1760
        if (!\is_array($this->showGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1761
            @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...
1762
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65.'
1763
                .' It will return only array in version 4.0.',
1764
                __METHOD__
1765
            ), E_USER_DEPRECATED);
1766
        }
1767
1768
        return $this->showGroups;
1769
    }
1770
1771
    public function setShowGroups(array $showGroups)
1772
    {
1773
        $this->showGroups = $showGroups;
1774
    }
1775
1776
    public function reorderShowGroup($group, array $keys)
1777
    {
1778
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1779
        $showGroups = $this->getShowGroups('sonata_deprecation_mute');
1780
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1781
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups('sonata_deprecation_mute') on line 1779 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...
1782
    }
1783
1784
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
1785
    {
1786
        $this->parentFieldDescription = $parentFieldDescription;
1787
    }
1788
1789
    public function getParentFieldDescription()
1790
    {
1791
        if (!$this->hasParentFieldDescription()) {
1792
            @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...
1793
                'Calling %s() when there is no parent field description is deprecated since'
1794
                .' sonata-project/admin-bundle 3.66 and will throw an exception in 4.0.'
1795
                .' Use %s::hasParentFieldDescription() to know if there is a parent field description.',
1796
                __METHOD__,
1797
                __CLASS__
1798
            ), E_USER_DEPRECATED);
1799
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1800
            // throw new \LogicException(sprintf(
1801
            //    'Admin "%s" has no parent field description.',
1802
            //    static::class
1803
            // ));
1804
1805
            return null;
1806
        }
1807
1808
        return $this->parentFieldDescription;
1809
    }
1810
1811
    public function hasParentFieldDescription()
1812
    {
1813
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1814
    }
1815
1816
    public function setSubject($subject)
1817
    {
1818
        if (\is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1819
            $message = <<<'EOT'
1820
You are trying to set entity an instance of "%s",
1821
which is not the one registered with this admin class ("%s").
1822
This is deprecated since 3.5 and will no longer be supported in 4.0.
1823
EOT;
1824
1825
            // NEXT_MAJOR : throw an exception instead
1826
            @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...
1827
        }
1828
1829
        $this->subject = $subject;
1830
    }
1831
1832
    public function getSubject()
1833
    {
1834
        if (!$this->hasSubject()) {
1835
            @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...
1836
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.66'
1837
                .' and will throw an exception in 4.0. Use %s::hasSubject() to know if there is a subject.',
1838
                __METHOD__,
1839
                __CLASS__
1840
            ), E_USER_DEPRECATED);
1841
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and update the return type
1842
            // throw new \LogicException(sprintf(
1843
            //    'Admin "%s" has no subject.',
1844
            //    static::class
1845
            // ));
1846
1847
            return null;
1848
        }
1849
1850
        return $this->subject;
1851
    }
1852
1853
    public function hasSubject()
1854
    {
1855
        if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
1856
            $id = $this->request->get($this->getIdParameter());
1857
1858
            if (null !== $id) {
1859
                $this->subject = $this->getObject($id);
1860
            }
1861
        }
1862
1863
        return null !== $this->subject;
1864
    }
1865
1866
    public function getFormFieldDescriptions()
1867
    {
1868
        $this->buildForm();
1869
1870
        return $this->formFieldDescriptions;
1871
    }
1872
1873
    public function getFormFieldDescription($name)
1874
    {
1875
        $this->buildForm();
1876
1877
        if (!$this->hasFormFieldDescription($name)) {
1878
            @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...
1879
                'Calling %s() when there is no form field description is deprecated since'
1880
                .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
1881
                .' Use %s::hasFormFieldDescription() to know if there is a form field description.',
1882
                __METHOD__,
1883
                __CLASS__
1884
            ), E_USER_DEPRECATED);
1885
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1886
            // throw new \LogicException(sprintf(
1887
            //    'Admin "%s" has no form field description for the field %s.',
1888
            //    static::class,
1889
            //    $name
1890
            // ));
1891
1892
            return null;
1893
        }
1894
1895
        return $this->formFieldDescriptions[$name];
1896
    }
1897
1898
    /**
1899
     * Returns true if the admin has a FieldDescription with the given $name.
1900
     *
1901
     * @param string $name
1902
     *
1903
     * @return bool
1904
     */
1905
    public function hasFormFieldDescription($name)
1906
    {
1907
        $this->buildForm();
1908
1909
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1910
    }
1911
1912
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1913
    {
1914
        $this->formFieldDescriptions[$name] = $fieldDescription;
1915
    }
1916
1917
    /**
1918
     * remove a FieldDescription.
1919
     *
1920
     * @param string $name
1921
     */
1922
    public function removeFormFieldDescription($name)
1923
    {
1924
        unset($this->formFieldDescriptions[$name]);
1925
    }
1926
1927
    /**
1928
     * build and return the collection of form FieldDescription.
1929
     *
1930
     * @return FieldDescriptionInterface[] collection of form FieldDescription
1931
     */
1932
    public function getShowFieldDescriptions()
1933
    {
1934
        $this->buildShow();
1935
1936
        return $this->showFieldDescriptions;
1937
    }
1938
1939
    /**
1940
     * Returns the form FieldDescription with the given $name.
1941
     *
1942
     * @param string $name
1943
     *
1944
     * @return FieldDescriptionInterface
1945
     */
1946
    public function getShowFieldDescription($name)
1947
    {
1948
        $this->buildShow();
1949
1950
        if (!$this->hasShowFieldDescription($name)) {
1951
            @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...
1952
                'Calling %s() when there is no show field description is deprecated since'
1953
                .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
1954
                .' Use %s::hasFormFieldDescription() to know if there is a show field description.',
1955
                __METHOD__,
1956
                __CLASS__
1957
            ), E_USER_DEPRECATED);
1958
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1959
            // throw new \LogicException(sprintf(
1960
            //    'Admin "%s" has no show field description for the field %s.',
1961
            //    static::class,
1962
            //    $name
1963
            // ));
1964
1965
            return null;
1966
        }
1967
1968
        return $this->showFieldDescriptions[$name];
1969
    }
1970
1971
    public function hasShowFieldDescription($name)
1972
    {
1973
        $this->buildShow();
1974
1975
        return \array_key_exists($name, $this->showFieldDescriptions);
1976
    }
1977
1978
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1979
    {
1980
        $this->showFieldDescriptions[$name] = $fieldDescription;
1981
    }
1982
1983
    public function removeShowFieldDescription($name)
1984
    {
1985
        unset($this->showFieldDescriptions[$name]);
1986
    }
1987
1988
    public function getListFieldDescriptions()
1989
    {
1990
        $this->buildList();
1991
1992
        return $this->listFieldDescriptions;
1993
    }
1994
1995
    public function getListFieldDescription($name)
1996
    {
1997
        $this->buildList();
1998
1999
        if (!$this->hasListFieldDescription($name)) {
2000
            @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...
2001
                'Calling %s() when there is no list field description is deprecated since'
2002
                .' sonata-project/admin-bundle 3.66 and will throw an exception in 4.0.'
2003
                .' Use %s::hasListFieldDescription(\'%s\') to know if there is a list field description.',
2004
                __METHOD__,
2005
                __CLASS__,
2006
                $name
2007
            ), E_USER_DEPRECATED);
2008
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
2009
            // throw new \LogicException(sprintf(
2010
            //    'Admin "%s" has no list field description for %s.',
2011
            //    static::class,
2012
            //    $name
2013
            // ));
2014
2015
            return null;
2016
        }
2017
2018
        return $this->listFieldDescriptions[$name];
2019
    }
2020
2021
    public function hasListFieldDescription($name)
2022
    {
2023
        $this->buildList();
2024
2025
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
2026
    }
2027
2028
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription)
2029
    {
2030
        $this->listFieldDescriptions[$name] = $fieldDescription;
2031
    }
2032
2033
    public function removeListFieldDescription($name)
2034
    {
2035
        unset($this->listFieldDescriptions[$name]);
2036
    }
2037
2038
    public function getFilterFieldDescription($name)
2039
    {
2040
        $this->buildDatagrid();
2041
2042
        if (!$this->hasFilterFieldDescription($name)) {
2043
            @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...
2044
                'Calling %s() when there is no filter field description is deprecated since'
2045
                .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
2046
                .' Use %s::hasFilterFieldDescription() to know if there is a filter field description.',
2047
                __METHOD__,
2048
                __CLASS__
2049
            ), E_USER_DEPRECATED);
2050
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
2051
            // throw new \LogicException(sprintf(
2052
            //    'Admin "%s" has no filter field description for the field %s.',
2053
            //    static::class,
2054
            //    $name
2055
            // ));
2056
2057
            return null;
2058
        }
2059
2060
        return $this->filterFieldDescriptions[$name];
2061
    }
2062
2063
    public function hasFilterFieldDescription($name)
2064
    {
2065
        $this->buildDatagrid();
2066
2067
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
2068
    }
2069
2070
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription)
2071
    {
2072
        $this->filterFieldDescriptions[$name] = $fieldDescription;
2073
    }
2074
2075
    public function removeFilterFieldDescription($name)
2076
    {
2077
        unset($this->filterFieldDescriptions[$name]);
2078
    }
2079
2080
    public function getFilterFieldDescriptions()
2081
    {
2082
        $this->buildDatagrid();
2083
2084
        return $this->filterFieldDescriptions;
2085
    }
2086
2087
    public function addChild(AdminInterface $child)
2088
    {
2089
        $parentAdmin = $this;
2090
        while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
2091
            $parentAdmin = $parentAdmin->getParent();
2092
        }
2093
2094
        if ($parentAdmin->getCode() === $child->getCode()) {
2095
            throw new \RuntimeException(sprintf(
2096
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
2097
                $child->getCode(),
2098
                $this->getCode()
2099
            ));
2100
        }
2101
2102
        $this->children[$child->getCode()] = $child;
2103
2104
        $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...
2105
2106
        // NEXT_MAJOR: remove $args and add $field parameter to this function on next Major
2107
2108
        $args = \func_get_args();
2109
2110
        if (isset($args[1])) {
2111
            $child->addParentAssociationMapping($this->getCode(), $args[1]);
2112
        } else {
2113
            @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...
2114
                'Calling "addChild" without second argument is deprecated since sonata-project/admin-bundle 3.35 and will not be allowed in 4.0.',
2115
                E_USER_DEPRECATED
2116
            );
2117
        }
2118
    }
2119
2120
    public function hasChild($code)
2121
    {
2122
        return isset($this->children[$code]);
2123
    }
2124
2125
    public function getChildren()
2126
    {
2127
        return $this->children;
2128
    }
2129
2130
    public function getChild($code)
2131
    {
2132
        if (!$this->hasChild($code)) {
2133
            @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...
2134
                'Calling %s() when there is no child is deprecated since sonata-project/admin-bundle 3.69'
2135
                .' and will throw an exception in 4.0. Use %s::hasChild() to know if the child exists.',
2136
                __METHOD__,
2137
                __CLASS__
2138
            ), E_USER_DEPRECATED);
2139
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
2140
            // throw new \LogicException(sprintf(
2141
            //    'Admin "%s" has no child for the code %s.',
2142
            //    static::class,
2143
            //    $code
2144
            // ));
2145
2146
            return null;
2147
        }
2148
2149
        return $this->children[$code];
2150
    }
2151
2152
    public function setParent(AdminInterface $parent)
2153
    {
2154
        $this->parent = $parent;
2155
    }
2156
2157
    public function getParent()
2158
    {
2159
        if (!$this->isChild()) {
2160
            @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...
2161
                'Calling %s() when there is no parent is deprecated since sonata-project/admin-bundle 3.66'
2162
                .' and will throw an exception in 4.0. Use %s::isChild() to know if there is a parent.',
2163
                __METHOD__,
2164
                __CLASS__
2165
            ), E_USER_DEPRECATED);
2166
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
2167
            // throw new \LogicException(sprintf(
2168
            //    'Admin "%s" has no parent.',
2169
            //    static::class
2170
            // ));
2171
2172
            return null;
2173
        }
2174
2175
        return $this->parent;
2176
    }
2177
2178
    final public function getRootAncestor()
2179
    {
2180
        $parent = $this;
2181
2182
        while ($parent->isChild()) {
2183
            $parent = $parent->getParent();
2184
        }
2185
2186
        return $parent;
2187
    }
2188
2189
    final public function getChildDepth()
2190
    {
2191
        $parent = $this;
2192
        $depth = 0;
2193
2194
        while ($parent->isChild()) {
2195
            $parent = $parent->getParent();
2196
            ++$depth;
2197
        }
2198
2199
        return $depth;
2200
    }
2201
2202
    final public function getCurrentLeafChildAdmin()
2203
    {
2204
        $child = $this->getCurrentChildAdmin();
2205
2206
        if (null === $child) {
2207
            return null;
2208
        }
2209
2210
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
2211
            $child = $c;
2212
        }
2213
2214
        return $child;
2215
    }
2216
2217
    public function isChild()
2218
    {
2219
        return $this->parent instanceof AdminInterface;
2220
    }
2221
2222
    /**
2223
     * Returns true if the admin has children, false otherwise.
2224
     *
2225
     * @return bool if the admin has children
2226
     */
2227
    public function hasChildren()
2228
    {
2229
        return \count($this->children) > 0;
2230
    }
2231
2232
    public function setUniqid($uniqid)
2233
    {
2234
        $this->uniqid = $uniqid;
2235
    }
2236
2237
    public function getUniqid()
2238
    {
2239
        if (!$this->uniqid) {
2240
            $this->uniqid = sprintf('s%s', uniqid());
2241
        }
2242
2243
        return $this->uniqid;
2244
    }
2245
2246
    /**
2247
     * Returns the classname label.
2248
     *
2249
     * @return string the classname label
2250
     */
2251
    public function getClassnameLabel()
2252
    {
2253
        return $this->classnameLabel;
2254
    }
2255
2256
    public function getPersistentParameters()
2257
    {
2258
        $parameters = [];
2259
2260
        foreach ($this->getExtensions() as $extension) {
2261
            $params = $extension->getPersistentParameters($this);
2262
2263
            if (!\is_array($params)) {
2264
                throw new \RuntimeException(sprintf(
2265
                    'The %s::getPersistentParameters must return an array',
2266
                    \get_class($extension)
2267
                ));
2268
            }
2269
2270
            $parameters = array_merge($parameters, $params);
2271
        }
2272
2273
        return $parameters;
2274
    }
2275
2276
    /**
2277
     * @param string $name
2278
     *
2279
     * @return mixed|null
2280
     */
2281
    public function getPersistentParameter($name)
2282
    {
2283
        $parameters = $this->getPersistentParameters();
2284
2285
        return $parameters[$name] ?? null;
2286
    }
2287
2288
    public function getBreadcrumbs($action)
2289
    {
2290
        @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...
2291
            'The %s method is deprecated since version 3.2 and will be removed in 4.0.'
2292
            .' Use %s::getBreadcrumbs instead.',
2293
            __METHOD__,
2294
            BreadcrumbsBuilder::class
2295
        ), E_USER_DEPRECATED);
2296
2297
        return $this->getBreadcrumbsBuilder()->getBreadcrumbs($this, $action);
2298
    }
2299
2300
    /**
2301
     * Generates the breadcrumbs array.
2302
     *
2303
     * Note: the method will be called by the top admin instance (parent => child)
2304
     *
2305
     * @param string $action
2306
     *
2307
     * @return array
2308
     */
2309
    public function buildBreadcrumbs($action, ?ItemInterface $menu = null)
2310
    {
2311
        @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...
2312
            'The %s method is deprecated since version 3.2 and will be removed in 4.0.',
2313
            __METHOD__
2314
        ), E_USER_DEPRECATED);
2315
2316
        if (isset($this->breadcrumbs[$action])) {
2317
            return $this->breadcrumbs[$action];
2318
        }
2319
2320
        return $this->breadcrumbs[$action] = $this->getBreadcrumbsBuilder()
2321
            ->buildBreadcrumbs($this, $action, $menu);
2322
    }
2323
2324
    /**
2325
     * NEXT_MAJOR : remove this method.
2326
     *
2327
     * @return BreadcrumbsBuilderInterface
2328
     */
2329
    final public function getBreadcrumbsBuilder()
2330
    {
2331
        @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...
2332
            'The %s method is deprecated since version 3.2 and will be removed in 4.0.'
2333
            .' Use the sonata.admin.breadcrumbs_builder service instead.',
2334
            __METHOD__
2335
        ), E_USER_DEPRECATED);
2336
        if (null === $this->breadcrumbsBuilder) {
2337
            $this->breadcrumbsBuilder = new BreadcrumbsBuilder(
2338
                $this->getConfigurationPool()->getContainer()->getParameter('sonata.admin.configuration.breadcrumbs')
2339
            );
2340
        }
2341
2342
        return $this->breadcrumbsBuilder;
2343
    }
2344
2345
    /**
2346
     * NEXT_MAJOR : remove this method.
2347
     *
2348
     * @return AbstractAdmin
2349
     */
2350
    final public function setBreadcrumbsBuilder(BreadcrumbsBuilderInterface $value)
2351
    {
2352
        @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...
2353
            'The %s method is deprecated since version 3.2 and will be removed in 4.0.'
2354
            .' Use the sonata.admin.breadcrumbs_builder service instead.',
2355
            __METHOD__
2356
        ), E_USER_DEPRECATED);
2357
        $this->breadcrumbsBuilder = $value;
2358
2359
        return $this;
2360
    }
2361
2362
    public function setCurrentChild($currentChild)
2363
    {
2364
        $this->currentChild = $currentChild;
2365
    }
2366
2367
    /**
2368
     * NEXT_MAJOR: Remove this method.
2369
     *
2370
     * @deprecated since sonata-project/admin-bundle 3.65, to be removed in 4.0
2371
     */
2372
    public function getCurrentChild()
2373
    {
2374
        @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...
2375
            'The %s() method is deprecated since version 3.65 and will be removed in 4.0.'
2376
            .' Use %s::isCurrentChild() instead.',
2377
            __METHOD__,
2378
            __CLASS__
2379
        ), E_USER_DEPRECATED);
2380
2381
        return $this->currentChild;
2382
    }
2383
2384
    public function isCurrentChild(): bool
2385
    {
2386
        return $this->currentChild;
2387
    }
2388
2389
    /**
2390
     * Returns the current child admin instance.
2391
     *
2392
     * @return AdminInterface|null the current child admin instance
2393
     */
2394
    public function getCurrentChildAdmin()
2395
    {
2396
        foreach ($this->children as $children) {
2397
            if ($children->isCurrentChild()) {
2398
                return $children;
2399
            }
2400
        }
2401
2402
        return null;
2403
    }
2404
2405
    public function trans($id, array $parameters = [], $domain = null, $locale = null)
2406
    {
2407
        @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...
2408
            'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2409
            __METHOD__
2410
        ), E_USER_DEPRECATED);
2411
2412
        $domain = $domain ?: $this->getTranslationDomain();
2413
2414
        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...
2415
    }
2416
2417
    /**
2418
     * Translate a message id.
2419
     *
2420
     * NEXT_MAJOR: remove this method
2421
     *
2422
     * @param string      $id
2423
     * @param int         $count
2424
     * @param string|null $domain
2425
     * @param string|null $locale
2426
     *
2427
     * @return string the translated string
2428
     *
2429
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2430
     */
2431
    public function transChoice($id, $count, array $parameters = [], $domain = null, $locale = null)
2432
    {
2433
        @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...
2434
            'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2435
            __METHOD__
2436
        ), E_USER_DEPRECATED);
2437
2438
        $domain = $domain ?: $this->getTranslationDomain();
2439
2440
        return $this->translator->transChoice($id, $count, $parameters, $domain, $locale);
0 ignored issues
show
Bug introduced by
The method transChoice() does not seem to exist on object<Symfony\Contracts...on\TranslatorInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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...
2441
    }
2442
2443
    public function setTranslationDomain($translationDomain)
2444
    {
2445
        $this->translationDomain = $translationDomain;
2446
    }
2447
2448
    public function getTranslationDomain()
2449
    {
2450
        return $this->translationDomain;
2451
    }
2452
2453
    /**
2454
     * {@inheritdoc}
2455
     *
2456
     * NEXT_MAJOR: remove this method
2457
     *
2458
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2459
     *
2460
     * @param DeprecatedTranslatorInterface|TranslatorInterface $translator
2461
     */
2462
    public function setTranslator($translator)
2463
    {
2464
        $args = \func_get_args();
2465
        if (isset($args[1]) && $args[1]) {
2466
            @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...
2467
                'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2468
                __METHOD__
2469
            ), E_USER_DEPRECATED);
2470
        }
2471
2472
        if (!$translator instanceof DeprecatedTranslatorInterface && !$translator instanceof TranslatorInterface) {
0 ignored issues
show
Bug introduced by
The class Symfony\Component\Translation\TranslatorInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
2473
            throw new \TypeError(sprintf(
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with sprintf('Argument 1 pass... \gettype($translator)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
2474
                'Argument 1 passed to "%s()" must be an instance of "%s" or "%s", %s given.',
2475
                __METHOD__,
2476
                DeprecatedTranslatorInterface::class,
2477
                TranslatorInterface::class,
2478
                \is_object($translator) ? 'instance of '.\get_class($translator) : \gettype($translator)
2479
            ));
2480
        }
2481
2482
        $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...
2483
    }
2484
2485
    /**
2486
     * {@inheritdoc}
2487
     *
2488
     * NEXT_MAJOR: remove this method
2489
     *
2490
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2491
     */
2492
    public function getTranslator()
2493
    {
2494
        @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...
2495
            'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
2496
            __METHOD__
2497
        ), E_USER_DEPRECATED);
2498
2499
        return $this->translator;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->translator; of type Symfony\Component\Transl...ion\TranslatorInterface adds the type Symfony\Contracts\Translation\TranslatorInterface to the return on line 2499 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getTranslator of type Symfony\Component\Translation\TranslatorInterface.
Loading history...
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...
2500
    }
2501
2502
    public function getTranslationLabel($label, $context = '', $type = '')
2503
    {
2504
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2505
    }
2506
2507
    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...
2508
    {
2509
        $this->request = $request;
2510
2511
        foreach ($this->getChildren() as $children) {
2512
            $children->setRequest($request);
2513
        }
2514
    }
2515
2516
    public function getRequest()
2517
    {
2518
        if (!$this->request) {
2519
            // NEXT_MAJOR: Throw \LogicException instead.
2520
            throw new \RuntimeException('The Request object has not been set');
2521
        }
2522
2523
        return $this->request;
2524
    }
2525
2526
    public function hasRequest()
2527
    {
2528
        return null !== $this->request;
2529
    }
2530
2531
    public function setFormContractor(FormContractorInterface $formBuilder)
2532
    {
2533
        $this->formContractor = $formBuilder;
2534
    }
2535
2536
    /**
2537
     * @return FormContractorInterface
2538
     */
2539
    public function getFormContractor()
2540
    {
2541
        return $this->formContractor;
2542
    }
2543
2544
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder)
2545
    {
2546
        $this->datagridBuilder = $datagridBuilder;
2547
    }
2548
2549
    public function getDatagridBuilder()
2550
    {
2551
        return $this->datagridBuilder;
2552
    }
2553
2554
    public function setListBuilder(ListBuilderInterface $listBuilder)
2555
    {
2556
        $this->listBuilder = $listBuilder;
2557
    }
2558
2559
    public function getListBuilder()
2560
    {
2561
        return $this->listBuilder;
2562
    }
2563
2564
    public function setShowBuilder(ShowBuilderInterface $showBuilder)
2565
    {
2566
        $this->showBuilder = $showBuilder;
2567
    }
2568
2569
    /**
2570
     * @return ShowBuilderInterface
2571
     */
2572
    public function getShowBuilder()
2573
    {
2574
        return $this->showBuilder;
2575
    }
2576
2577
    public function setConfigurationPool(Pool $configurationPool)
2578
    {
2579
        $this->configurationPool = $configurationPool;
2580
    }
2581
2582
    /**
2583
     * @return Pool
2584
     */
2585
    public function getConfigurationPool()
2586
    {
2587
        return $this->configurationPool;
2588
    }
2589
2590
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator)
2591
    {
2592
        $this->routeGenerator = $routeGenerator;
2593
    }
2594
2595
    /**
2596
     * @return RouteGeneratorInterface
2597
     */
2598
    public function getRouteGenerator()
2599
    {
2600
        return $this->routeGenerator;
2601
    }
2602
2603
    public function getCode()
2604
    {
2605
        return $this->code;
2606
    }
2607
2608
    /**
2609
     * NEXT_MAJOR: Remove this function.
2610
     *
2611
     * @deprecated This method is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0
2612
     *
2613
     * @param string $baseCodeRoute
2614
     */
2615
    public function setBaseCodeRoute($baseCodeRoute)
2616
    {
2617
        @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...
2618
            'The %s is deprecated since 3.24 and will be removed in 4.0.',
2619
            __METHOD__
2620
        ), E_USER_DEPRECATED);
2621
2622
        $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...
2623
    }
2624
2625
    public function getBaseCodeRoute()
2626
    {
2627
        // NEXT_MAJOR: Uncomment the following lines.
2628
        // if ($this->isChild()) {
2629
        //     return sprintf('%s|%s', $this->getParent()->getBaseCodeRoute(), $this->getCode());
2630
        // }
2631
        //
2632
        // return $this->getCode();
2633
2634
        // NEXT_MAJOR: Remove all the code below.
2635
        if ($this->isChild()) {
2636
            $parentCode = $this->getParent()->getCode();
2637
2638
            if ($this->getParent()->isChild()) {
2639
                $parentCode = $this->getParent()->getBaseCodeRoute();
2640
            }
2641
2642
            return sprintf('%s|%s', $parentCode, $this->getCode());
2643
        }
2644
2645
        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...
2646
    }
2647
2648
    public function getModelManager()
2649
    {
2650
        return $this->modelManager;
2651
    }
2652
2653
    public function setModelManager(ModelManagerInterface $modelManager)
2654
    {
2655
        $this->modelManager = $modelManager;
2656
    }
2657
2658
    public function getManagerType()
2659
    {
2660
        return $this->managerType;
2661
    }
2662
2663
    /**
2664
     * @param string $type
2665
     */
2666
    public function setManagerType($type)
2667
    {
2668
        $this->managerType = $type;
2669
    }
2670
2671
    public function getObjectIdentifier()
2672
    {
2673
        return $this->getCode();
2674
    }
2675
2676
    /**
2677
     * Set the roles and permissions per role.
2678
     */
2679
    public function setSecurityInformation(array $information)
2680
    {
2681
        $this->securityInformation = $information;
2682
    }
2683
2684
    public function getSecurityInformation()
2685
    {
2686
        return $this->securityInformation;
2687
    }
2688
2689
    /**
2690
     * Return the list of permissions the user should have in order to display the admin.
2691
     *
2692
     * @param string $context
2693
     *
2694
     * @return array
2695
     */
2696
    public function getPermissionsShow($context)
2697
    {
2698
        switch ($context) {
2699
            case self::CONTEXT_DASHBOARD:
2700
            case self::CONTEXT_MENU:
2701
            default:
2702
                return ['LIST'];
2703
        }
2704
    }
2705
2706
    public function showIn($context)
2707
    {
2708
        switch ($context) {
2709
            case self::CONTEXT_DASHBOARD:
2710
            case self::CONTEXT_MENU:
2711
            default:
2712
                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...
2713
        }
2714
    }
2715
2716
    public function createObjectSecurity($object)
2717
    {
2718
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2719
    }
2720
2721
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
2722
    {
2723
        $this->securityHandler = $securityHandler;
2724
    }
2725
2726
    public function getSecurityHandler()
2727
    {
2728
        return $this->securityHandler;
2729
    }
2730
2731
    public function isGranted($name, $object = null)
2732
    {
2733
        $objectRef = $object ? sprintf('/%s#%s', spl_object_hash($object), $this->id($object)) : '';
2734
        $key = md5(json_encode($name).$objectRef);
2735
2736
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
2737
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2738
        }
2739
2740
        return $this->cacheIsGranted[$key];
2741
    }
2742
2743
    public function getUrlSafeIdentifier($model)
2744
    {
2745
        return $this->getModelManager()->getUrlSafeIdentifier($model);
2746
    }
2747
2748
    public function getNormalizedIdentifier($model)
2749
    {
2750
        return $this->getModelManager()->getNormalizedIdentifier($model);
2751
    }
2752
2753
    public function id($model)
2754
    {
2755
        return $this->getNormalizedIdentifier($model);
2756
    }
2757
2758
    public function setValidator($validator)
2759
    {
2760
        // NEXT_MAJOR: Move ValidatorInterface check to method signature
2761
        if (!$validator instanceof ValidatorInterface) {
2762
            throw new \InvalidArgumentException(sprintf(
2763
                'Argument 1 must be an instance of %s',
2764
                ValidatorInterface::class
2765
            ));
2766
        }
2767
2768
        $this->validator = $validator;
2769
    }
2770
2771
    public function getValidator()
2772
    {
2773
        return $this->validator;
2774
    }
2775
2776
    public function getShow()
2777
    {
2778
        $this->buildShow();
2779
2780
        return $this->show;
2781
    }
2782
2783
    public function setFormTheme(array $formTheme)
2784
    {
2785
        $this->formTheme = $formTheme;
2786
    }
2787
2788
    public function getFormTheme()
2789
    {
2790
        return $this->formTheme;
2791
    }
2792
2793
    public function setFilterTheme(array $filterTheme)
2794
    {
2795
        $this->filterTheme = $filterTheme;
2796
    }
2797
2798
    public function getFilterTheme()
2799
    {
2800
        return $this->filterTheme;
2801
    }
2802
2803
    public function addExtension(AdminExtensionInterface $extension)
2804
    {
2805
        $this->extensions[] = $extension;
2806
    }
2807
2808
    public function getExtensions()
2809
    {
2810
        return $this->extensions;
2811
    }
2812
2813
    public function setMenuFactory(FactoryInterface $menuFactory)
2814
    {
2815
        $this->menuFactory = $menuFactory;
2816
    }
2817
2818
    public function getMenuFactory()
2819
    {
2820
        return $this->menuFactory;
2821
    }
2822
2823
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder)
2824
    {
2825
        $this->routeBuilder = $routeBuilder;
2826
    }
2827
2828
    public function getRouteBuilder()
2829
    {
2830
        return $this->routeBuilder;
2831
    }
2832
2833
    public function toString($object)
2834
    {
2835
        if (!\is_object($object)) {
2836
            return '';
2837
        }
2838
2839
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2840
            return (string) $object;
2841
        }
2842
2843
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2844
    }
2845
2846
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy)
2847
    {
2848
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2849
    }
2850
2851
    public function getLabelTranslatorStrategy()
2852
    {
2853
        return $this->labelTranslatorStrategy;
2854
    }
2855
2856
    public function supportsPreviewMode()
2857
    {
2858
        return $this->supportsPreviewMode;
2859
    }
2860
2861
    /**
2862
     * NEXT_MAJOR: Remove this.
2863
     *
2864
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
2865
     *
2866
     * Set custom per page options.
2867
     */
2868
    public function setPerPageOptions(array $options)
2869
    {
2870
        @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...
2871
            'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
2872
            __METHOD__
2873
        ), E_USER_DEPRECATED);
2874
2875
        $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...
2876
    }
2877
2878
    /**
2879
     * Returns predefined per page options.
2880
     *
2881
     * @return array
2882
     */
2883
    public function getPerPageOptions()
2884
    {
2885
        // NEXT_MAJOR: Remove this line and uncomment the following
2886
        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...
2887
//        $perPageOptions = $this->getModelManager()->getDefaultPerPageOptions($this->class);
2888
//        $perPageOptions[] = $this->getMaxPerPage();
2889
//
2890
//        $perPageOptions = array_unique($perPageOptions);
2891
//        sort($perPageOptions);
2892
//
2893
//        return $perPageOptions;
2894
    }
2895
2896
    /**
2897
     * Set pager type.
2898
     *
2899
     * @param string $pagerType
2900
     */
2901
    public function setPagerType($pagerType)
2902
    {
2903
        $this->pagerType = $pagerType;
2904
    }
2905
2906
    /**
2907
     * Get pager type.
2908
     *
2909
     * @return string
2910
     */
2911
    public function getPagerType()
2912
    {
2913
        return $this->pagerType;
2914
    }
2915
2916
    /**
2917
     * Returns true if the per page value is allowed, false otherwise.
2918
     *
2919
     * @param int $perPage
2920
     *
2921
     * @return bool
2922
     */
2923
    public function determinedPerPageValue($perPage)
2924
    {
2925
        return \in_array($perPage, $this->getPerPageOptions(), true);
2926
    }
2927
2928
    public function isAclEnabled()
2929
    {
2930
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2931
    }
2932
2933
    public function getObjectMetadata($object)
2934
    {
2935
        return new Metadata($this->toString($object));
2936
    }
2937
2938
    public function getListModes()
2939
    {
2940
        return $this->listModes;
2941
    }
2942
2943
    public function setListMode($mode)
2944
    {
2945
        if (!$this->hasRequest()) {
2946
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2947
        }
2948
2949
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2950
    }
2951
2952
    public function getListMode()
2953
    {
2954
        if (!$this->hasRequest()) {
2955
            return 'list';
2956
        }
2957
2958
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2959
    }
2960
2961
    public function getAccessMapping()
2962
    {
2963
        return $this->accessMapping;
2964
    }
2965
2966
    public function checkAccess($action, $object = null)
2967
    {
2968
        $access = $this->getAccess();
2969
2970
        if (!\array_key_exists($action, $access)) {
2971
            throw new \InvalidArgumentException(sprintf(
2972
                'Action "%s" could not be found in access mapping.'
2973
                .' Please make sure your action is defined into your admin class accessMapping property.',
2974
                $action
2975
            ));
2976
        }
2977
2978
        if (!\is_array($access[$action])) {
2979
            $access[$action] = [$access[$action]];
2980
        }
2981
2982
        foreach ($access[$action] as $role) {
2983
            if (false === $this->isGranted($role, $object)) {
2984
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2985
            }
2986
        }
2987
    }
2988
2989
    /**
2990
     * Hook to handle access authorization, without throw Exception.
2991
     *
2992
     * @param string $action
2993
     * @param object $object
2994
     *
2995
     * @return bool
2996
     */
2997
    public function hasAccess($action, $object = null)
2998
    {
2999
        $access = $this->getAccess();
3000
3001
        if (!\array_key_exists($action, $access)) {
3002
            return false;
3003
        }
3004
3005
        if (!\is_array($access[$action])) {
3006
            $access[$action] = [$access[$action]];
3007
        }
3008
3009
        foreach ($access[$action] as $role) {
3010
            if (false === $this->isGranted($role, $object)) {
3011
                return false;
3012
            }
3013
        }
3014
3015
        return true;
3016
    }
3017
3018
    /**
3019
     * @param string      $action
3020
     * @param object|null $object
3021
     *
3022
     * @return array
3023
     */
3024
    public function configureActionButtons($action, $object = null)
3025
    {
3026
        $list = [];
3027
3028
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
3029
            && $this->hasAccess('create')
3030
            && $this->hasRoute('create')
3031
        ) {
3032
            $list['create'] = [
3033
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3034
                '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...
3035
//                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
3036
            ];
3037
        }
3038
3039
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
3040
            && $this->canAccessObject('edit', $object)
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 3024 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...
3041
            && $this->hasRoute('edit')
3042
        ) {
3043
            $list['edit'] = [
3044
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3045
                '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...
3046
                //'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
3047
            ];
3048
        }
3049
3050
        if (\in_array($action, ['show', 'edit', 'acl'], true)
3051
            && $this->canAccessObject('history', $object)
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 3024 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...
3052
            && $this->hasRoute('history')
3053
        ) {
3054
            $list['history'] = [
3055
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3056
                '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...
3057
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
3058
            ];
3059
        }
3060
3061
        if (\in_array($action, ['edit', 'history'], true)
3062
            && $this->isAclEnabled()
3063
            && $this->canAccessObject('acl', $object)
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 3024 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...
3064
            && $this->hasRoute('acl')
3065
        ) {
3066
            $list['acl'] = [
3067
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3068
                '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...
3069
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
3070
            ];
3071
        }
3072
3073
        if (\in_array($action, ['edit', 'history', 'acl'], true)
3074
            && $this->canAccessObject('show', $object)
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 3024 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...
3075
            && \count($this->getShow()) > 0
3076
            && $this->hasRoute('show')
3077
        ) {
3078
            $list['show'] = [
3079
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3080
                '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...
3081
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
3082
            ];
3083
        }
3084
3085
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
3086
            && $this->hasAccess('list')
3087
            && $this->hasRoute('list')
3088
        ) {
3089
            $list['list'] = [
3090
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3091
                '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...
3092
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
3093
            ];
3094
        }
3095
3096
        return $list;
3097
    }
3098
3099
    /**
3100
     * @param string $action
3101
     * @param object $object
3102
     *
3103
     * @return array
3104
     */
3105
    public function getActionButtons($action, $object = null)
3106
    {
3107
        $list = $this->configureActionButtons($action, $object);
3108
3109
        foreach ($this->getExtensions() as $extension) {
3110
            // NEXT_MAJOR: remove method check
3111
            if (method_exists($extension, 'configureActionButtons')) {
3112
                $list = $extension->configureActionButtons($this, $list, $action, $object);
3113
            }
3114
        }
3115
3116
        return $list;
3117
    }
3118
3119
    /**
3120
     * Get the list of actions that can be accessed directly from the dashboard.
3121
     *
3122
     * @return array
3123
     */
3124
    public function getDashboardActions()
3125
    {
3126
        $actions = [];
3127
3128
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
3129
            $actions['create'] = [
3130
                'label' => 'link_add',
3131
                'translation_domain' => 'SonataAdminBundle',
3132
                // NEXT_MAJOR: Remove this line and use commented line below it instead
3133
                '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...
3134
                // 'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
3135
                'url' => $this->generateUrl('create'),
3136
                'icon' => 'plus-circle',
3137
            ];
3138
        }
3139
3140
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
3141
            $actions['list'] = [
3142
                'label' => 'link_list',
3143
                'translation_domain' => 'SonataAdminBundle',
3144
                'url' => $this->generateUrl('list'),
3145
                'icon' => 'list',
3146
            ];
3147
        }
3148
3149
        return $actions;
3150
    }
3151
3152
    /**
3153
     * Setting to true will enable mosaic button for the admin screen.
3154
     * Setting to false will hide mosaic button for the admin screen.
3155
     *
3156
     * @param bool $isShown
3157
     */
3158
    final public function showMosaicButton($isShown)
3159
    {
3160
        if ($isShown) {
3161
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
3162
        } else {
3163
            unset($this->listModes['mosaic']);
3164
        }
3165
    }
3166
3167
    /**
3168
     * @param object $object
3169
     */
3170
    final public function getSearchResultLink($object)
3171
    {
3172
        foreach ($this->searchResultActions as $action) {
3173
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
3174
                return $this->generateObjectUrl($action, $object);
3175
            }
3176
        }
3177
3178
        return null;
3179
    }
3180
3181
    /**
3182
     * NEXT_MAJOR: remove this method.
3183
     *
3184
     * Checks if a filter type is set to a default value.
3185
     *
3186
     * @param string $name
3187
     *
3188
     * @return bool
3189
     */
3190
    final public function isDefaultFilter($name)
3191
    {
3192
        @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...
3193
            'Method "%s" is deprecated since sonata-project/admin-bundle 3.x.',
3194
            __METHOD__
3195
        ), E_USER_DEPRECATED);
3196
3197
        $filter = $this->getFilterParameters();
3198
        $default = $this->getDefaultFilterValues();
3199
3200
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
3201
            return false;
3202
        }
3203
3204
        return $filter[$name] === $default[$name];
3205
    }
3206
3207
    /**
3208
     * Check object existence and access, without throw Exception.
3209
     *
3210
     * @param string $action
3211
     * @param object $object
3212
     *
3213
     * @return bool
3214
     */
3215
    public function canAccessObject($action, $object)
3216
    {
3217
        return $object && $this->id($object) && $this->hasAccess($action, $object);
3218
    }
3219
3220
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
3221
    {
3222
        return $query;
3223
    }
3224
3225
    /**
3226
     * @return MutableTemplateRegistryInterface
3227
     */
3228
    final protected function getTemplateRegistry()
3229
    {
3230
        return $this->templateRegistry;
3231
    }
3232
3233
    /**
3234
     * Returns a list of default sort values.
3235
     *
3236
     * @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...
3237
     */
3238
    final protected function getDefaultSortValues(): array
3239
    {
3240
        $defaultSortValues = [];
3241
3242
        $this->configureDefaultSortValues($defaultSortValues);
3243
3244
        foreach ($this->getExtensions() as $extension) {
3245
            // NEXT_MAJOR: remove method check
3246
            if (method_exists($extension, 'configureDefaultSortValues')) {
3247
                $extension->configureDefaultSortValues($this, $defaultSortValues);
3248
            }
3249
        }
3250
3251
        return $defaultSortValues;
3252
    }
3253
3254
    /**
3255
     * Returns a list of default filters.
3256
     *
3257
     * @return array
3258
     */
3259
    final protected function getDefaultFilterValues()
3260
    {
3261
        $defaultFilterValues = [];
3262
3263
        $this->configureDefaultFilterValues($defaultFilterValues);
3264
3265
        foreach ($this->getExtensions() as $extension) {
3266
            // NEXT_MAJOR: remove method check
3267
            if (method_exists($extension, 'configureDefaultFilterValues')) {
3268
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
3269
            }
3270
        }
3271
3272
        return $defaultFilterValues;
3273
    }
3274
3275
    protected function configureFormFields(FormMapper $form)
3276
    {
3277
    }
3278
3279
    protected function configureListFields(ListMapper $list)
3280
    {
3281
    }
3282
3283
    protected function configureDatagridFilters(DatagridMapper $filter)
3284
    {
3285
    }
3286
3287
    protected function configureShowFields(ShowMapper $show)
3288
    {
3289
    }
3290
3291
    protected function configureRoutes(RouteCollection $collection)
3292
    {
3293
    }
3294
3295
    /**
3296
     * Allows you to customize batch actions.
3297
     *
3298
     * @param array $actions List of actions
3299
     *
3300
     * @return array
3301
     */
3302
    protected function configureBatchActions($actions)
3303
    {
3304
        return $actions;
3305
    }
3306
3307
    /**
3308
     * NEXT_MAJOR: remove this method.
3309
     *
3310
     * @deprecated Use configureTabMenu instead
3311
     */
3312
    protected function configureSideMenu(ItemInterface $menu, string $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...
3313
    {
3314
    }
3315
3316
    /**
3317
     * Configures the tab menu in your admin.
3318
     *
3319
     * @param string $action
3320
     */
3321
    protected function configureTabMenu(ItemInterface $menu, $action, ?AdminInterface $childAdmin = null)
3322
    {
3323
        // Use configureSideMenu not to mess with previous overrides
3324
        // NEXT_MAJOR: remove this line
3325
        $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...
3326
    }
3327
3328
    /**
3329
     * build the view FieldDescription array.
3330
     */
3331
    protected function buildShow()
3332
    {
3333
        if ($this->loaded['show']) {
3334
            return;
3335
        }
3336
3337
        $this->loaded['show'] = true;
3338
3339
        $this->show = $this->getShowBuilder()->getBaseList();
3340
        $mapper = new ShowMapper($this->getShowBuilder(), $this->show, $this);
3341
3342
        $this->configureShowFields($mapper);
3343
3344
        foreach ($this->getExtensions() as $extension) {
3345
            $extension->configureShowFields($mapper);
3346
        }
3347
    }
3348
3349
    /**
3350
     * build the list FieldDescription array.
3351
     */
3352
    protected function buildList()
3353
    {
3354
        if ($this->loaded['list']) {
3355
            return;
3356
        }
3357
3358
        $this->loaded['list'] = true;
3359
3360
        $this->list = $this->getListBuilder()->getBaseList();
3361
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
3362
3363
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
3364
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3365
                $this->getClass(),
3366
                'batch',
3367
                [
3368
                    'label' => 'batch',
3369
                    'code' => '_batch',
3370
                    'sortable' => false,
3371
                    'virtual_field' => true,
3372
                ]
3373
            );
3374
3375
            $fieldDescription->setAdmin($this);
3376
            // NEXT_MAJOR: Remove this line and use commented line below it instead
3377
            $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...
3378
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
3379
3380
            $mapper->add($fieldDescription, ListMapper::TYPE_BATCH);
3381
        }
3382
3383
        $this->configureListFields($mapper);
3384
3385
        foreach ($this->getExtensions() as $extension) {
3386
            $extension->configureListFields($mapper);
3387
        }
3388
3389
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
3390
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3391
                $this->getClass(),
3392
                'select',
3393
                [
3394
                    'label' => false,
3395
                    'code' => '_select',
3396
                    'sortable' => false,
3397
                    'virtual_field' => false,
3398
                ]
3399
            );
3400
3401
            $fieldDescription->setAdmin($this);
3402
            // NEXT_MAJOR: Remove this line and use commented line below it instead
3403
            $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...
3404
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
3405
3406
            $mapper->add($fieldDescription, ListMapper::TYPE_SELECT);
3407
        }
3408
    }
3409
3410
    /**
3411
     * Build the form FieldDescription collection.
3412
     */
3413
    protected function buildForm()
3414
    {
3415
        if ($this->loaded['form']) {
3416
            return;
3417
        }
3418
3419
        $this->loaded['form'] = true;
3420
3421
        $formBuilder = $this->getFormBuilder();
3422
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
3423
            $this->preValidate($event->getData());
3424
        }, 100);
3425
3426
        $this->form = $formBuilder->getForm();
3427
    }
3428
3429
    /**
3430
     * Gets the subclass corresponding to the given name.
3431
     *
3432
     * @param string $name The name of the sub class
3433
     *
3434
     * @return string the subclass
3435
     */
3436
    protected function getSubClass($name)
3437
    {
3438
        if ($this->hasSubClass($name)) {
3439
            return $this->subClasses[$name];
3440
        }
3441
3442
        // NEXT_MAJOR: Throw \LogicException instead.
3443
        throw new \RuntimeException(sprintf('Unable to find the subclass `%s` for admin `%s`', $name, static::class));
3444
    }
3445
3446
    /**
3447
     * Attach the inline validator to the model metadata, this must be done once per admin.
3448
     */
3449
    protected function attachInlineValidator()
3450
    {
3451
        $admin = $this;
3452
3453
        // add the custom inline validation option
3454
        $metadata = $this->validator->getMetadataFor($this->getClass());
3455
        if (!$metadata instanceof GenericMetadata) {
3456
            throw new \UnexpectedValueException(
3457
                sprintf(
3458
                    'Cannot add inline validator for %s because its metadata is an instance of %s instead of %s',
3459
                    $this->getClass(),
3460
                    \get_class($metadata),
3461
                    GenericMetadata::class
3462
                )
3463
            );
3464
        }
3465
3466
        $metadata->addConstraint(new InlineConstraint([
3467
            'service' => $this,
3468
            'method' => static function (ErrorElement $errorElement, $object) use ($admin) {
3469
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
3470
3471
                // This avoid the main validation to be cascaded to children
3472
                // The problem occurs when a model Page has a collection of Page as property
3473
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
3474
                    return;
3475
                }
3476
3477
                $admin->validate($errorElement, $object);
3478
3479
                foreach ($admin->getExtensions() as $extension) {
3480
                    $extension->validate($admin, $errorElement, $object);
3481
                }
3482
            },
3483
            'serializingWarning' => true,
3484
        ]));
3485
    }
3486
3487
    /**
3488
     * NEXT_MAJOR: Remove this function.
3489
     *
3490
     * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
3491
     *
3492
     * Predefine per page options.
3493
     */
3494
    protected function predefinePerPageOptions()
3495
    {
3496
        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...
3497
        $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...
3498
        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...
3499
    }
3500
3501
    /**
3502
     * Return list routes with permissions name.
3503
     *
3504
     * @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...
3505
     */
3506
    protected function getAccess()
3507
    {
3508
        $access = array_merge([
3509
            'acl' => 'MASTER',
3510
            'export' => 'EXPORT',
3511
            'historyCompareRevisions' => 'EDIT',
3512
            'historyViewRevision' => 'EDIT',
3513
            'history' => 'EDIT',
3514
            'edit' => 'EDIT',
3515
            'show' => 'VIEW',
3516
            'create' => 'CREATE',
3517
            'delete' => 'DELETE',
3518
            'batchDelete' => 'DELETE',
3519
            'list' => 'LIST',
3520
        ], $this->getAccessMapping());
3521
3522
        foreach ($this->extensions as $extension) {
3523
            // NEXT_MAJOR: remove method check
3524
            if (method_exists($extension, 'getAccessMapping')) {
3525
                $access = array_merge($access, $extension->getAccessMapping($this));
3526
            }
3527
        }
3528
3529
        return $access;
3530
    }
3531
3532
    /**
3533
     * Configures a list of default filters.
3534
     */
3535
    protected function configureDefaultFilterValues(array &$filterValues)
3536
    {
3537
    }
3538
3539
    /**
3540
     * Configures a list of default sort values.
3541
     *
3542
     * Example:
3543
     *   $sortValues['_sort_by'] = 'foo'
3544
     *   $sortValues['_sort_order'] = 'DESC'
3545
     */
3546
    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...
3547
    {
3548
    }
3549
3550
    /**
3551
     * Set the parent object, if any, to the provided object.
3552
     */
3553
    final protected function appendParentObject(object $object): void
3554
    {
3555
        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...
3556
            $parentAdmin = $this->getParent();
3557
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
3558
3559
            if (null !== $parentObject) {
3560
                $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3561
                $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3562
3563
                $value = $propertyAccessor->getValue($object, $propertyPath);
3564
3565
                if (\is_array($value) || $value instanceof \ArrayAccess) {
3566
                    $value[] = $parentObject;
3567
                    $propertyAccessor->setValue($object, $propertyPath, $value);
3568
                } else {
3569
                    $propertyAccessor->setValue($object, $propertyPath, $parentObject);
3570
                }
3571
            }
3572
        } elseif ($this->hasParentFieldDescription()) {
3573
            $parentAdmin = $this->getParentFieldDescription()->getAdmin();
3574
            $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter()));
3575
3576
            if (null !== $parentObject) {
3577
                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...
3578
            }
3579
        }
3580
    }
3581
3582
    /**
3583
     * Build all the related urls to the current admin.
3584
     */
3585
    private function buildRoutes(): void
3586
    {
3587
        if ($this->loaded['routes']) {
3588
            return;
3589
        }
3590
3591
        $this->loaded['routes'] = true;
3592
3593
        $this->routes = new RouteCollection(
3594
            $this->getBaseCodeRoute(),
3595
            $this->getBaseRouteName(),
3596
            $this->getBaseRoutePattern(),
3597
            $this->getBaseControllerName()
3598
        );
3599
3600
        $this->routeBuilder->build($this, $this->routes);
3601
3602
        $this->configureRoutes($this->routes);
3603
3604
        foreach ($this->getExtensions() as $extension) {
3605
            $extension->configureRoutes($this, $this->routes);
3606
        }
3607
    }
3608
}
3609
3610
class_exists(\Sonata\Form\Validator\ErrorElement::class);
3611