Completed
Push — master ( 5c73c5...651a01 )
by Javier
17s queued 11s
created

AbstractAdmin::configureDefaultSortValues()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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...
1450
    }
1451
1452
    /**
1453
     * @return int
1454
     */
1455
    public function getMaxPerPage()
1456
    {
1457
        // NEXT_MAJOR: Remove this line and uncomment the following.
1458
        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.x.

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...
1459
        // $sortValues = $this->getModelManager()->getDefaultSortValues($this->class);
1460
1461
        // return $sortValues['_per_page'] ?? 25;
1462
    }
1463
1464
    /**
1465
     * @param int $maxPageLinks
1466
     */
1467
    public function setMaxPageLinks($maxPageLinks): void
1468
    {
1469
        $this->maxPageLinks = $maxPageLinks;
1470
    }
1471
1472
    /**
1473
     * @return int
1474
     */
1475
    public function getMaxPageLinks()
1476
    {
1477
        return $this->maxPageLinks;
1478
    }
1479
1480
    public function getFormGroups()
1481
    {
1482
        if (!\is_array($this->formGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1483
            @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...
1484
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65. It will return only array in version 4.0.',
1485
                __METHOD__
1486
            ), E_USER_DEPRECATED);
1487
        }
1488
1489
        return $this->formGroups;
1490
    }
1491
1492
    public function setFormGroups(array $formGroups): void
1493
    {
1494
        $this->formGroups = $formGroups;
1495
    }
1496
1497
    public function removeFieldFromFormGroup($key): void
1498
    {
1499
        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...
1500
            unset($this->formGroups[$name]['fields'][$key]);
1501
1502
            if (empty($this->formGroups[$name]['fields'])) {
1503
                unset($this->formGroups[$name]);
1504
            }
1505
        }
1506
    }
1507
1508
    /**
1509
     * @param string $group
1510
     */
1511
    public function reorderFormGroup($group, array $keys): void
1512
    {
1513
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1514
        $formGroups = $this->getFormGroups('sonata_deprecation_mute');
1515
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1516
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups('sonata_deprecation_mute') on line 1514 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...
1517
    }
1518
1519
    public function getFormTabs()
1520
    {
1521
        if (!\is_array($this->formTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1522
            @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...
1523
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65. It will return only array in version 4.0.',
1524
                __METHOD__
1525
            ), E_USER_DEPRECATED);
1526
        }
1527
1528
        return $this->formTabs;
1529
    }
1530
1531
    public function setFormTabs(array $formTabs): void
1532
    {
1533
        $this->formTabs = $formTabs;
1534
    }
1535
1536
    public function getShowTabs()
1537
    {
1538
        if (!\is_array($this->showTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1539
            @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...
1540
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65. It will return only array in version 4.0.',
1541
                __METHOD__
1542
            ), E_USER_DEPRECATED);
1543
        }
1544
1545
        return $this->showTabs;
1546
    }
1547
1548
    public function setShowTabs(array $showTabs): void
1549
    {
1550
        $this->showTabs = $showTabs;
1551
    }
1552
1553
    public function getShowGroups()
1554
    {
1555
        if (!\is_array($this->showGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1556
            @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...
1557
                'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65. It will return only array in version 4.0.',
1558
                __METHOD__
1559
            ), E_USER_DEPRECATED);
1560
        }
1561
1562
        return $this->showGroups;
1563
    }
1564
1565
    public function setShowGroups(array $showGroups): void
1566
    {
1567
        $this->showGroups = $showGroups;
1568
    }
1569
1570
    public function reorderShowGroup($group, array $keys): void
1571
    {
1572
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1573
        $showGroups = $this->getShowGroups('sonata_deprecation_mute');
1574
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1575
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups('sonata_deprecation_mute') on line 1573 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...
1576
    }
1577
1578
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription): void
1579
    {
1580
        $this->parentFieldDescription = $parentFieldDescription;
1581
    }
1582
1583
    public function getParentFieldDescription()
1584
    {
1585
        if (!$this->hasParentFieldDescription()) {
1586
            @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...
1587
                'Calling %s() when there is no parent field description is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
1588
                'Use %s::hasParentFieldDescription() to know if there is a parent field description.',
1589
                __METHOD__,
1590
                __CLASS__
1591
            ), E_USER_DEPRECATED);
1592
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1593
            // throw new \LogicException(sprintf(
1594
            //    'Admin "%s" has no parent field description.',
1595
            //    static::class
1596
            // ));
1597
1598
            return null;
1599
        }
1600
1601
        return $this->parentFieldDescription;
1602
    }
1603
1604
    public function hasParentFieldDescription()
1605
    {
1606
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1607
    }
1608
1609
    public function setSubject($subject): void
1610
    {
1611
        if (\is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1612
            $message = <<<'EOT'
1613
You are trying to set entity an instance of "%s",
1614
which is not the one registered with this admin class ("%s").
1615
This is deprecated since 3.5 and will no longer be supported in 4.0.
1616
EOT;
1617
1618
            @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...
1619
                sprintf($message, \get_class($subject), $this->getClass()),
1620
                E_USER_DEPRECATED
1621
            ); // NEXT_MAJOR : throw an exception instead
1622
        }
1623
1624
        $this->subject = $subject;
1625
    }
1626
1627
    public function getSubject()
1628
    {
1629
        if (!$this->hasSubject()) {
1630
            @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...
1631
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
1632
                'Use %s::hasSubject() to know if there is a subject.',
1633
                __METHOD__,
1634
                __CLASS__
1635
            ), E_USER_DEPRECATED);
1636
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and update the return type
1637
            // throw new \LogicException(sprintf(
1638
            //    'Admin "%s" has no subject.',
1639
            //    static::class
1640
            // ));
1641
1642
            return null;
1643
        }
1644
1645
        return $this->subject;
1646
    }
1647
1648
    public function hasSubject()
1649
    {
1650
        if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
1651
            $id = $this->request->get($this->getIdParameter());
1652
1653
            if (null !== $id) {
1654
                $this->subject = $this->getObject($id);
1655
            }
1656
        }
1657
1658
        return null !== $this->subject;
1659
    }
1660
1661
    public function getFormFieldDescriptions()
1662
    {
1663
        $this->buildForm();
1664
1665
        return $this->formFieldDescriptions;
1666
    }
1667
1668
    public function getFormFieldDescription($name)
1669
    {
1670
        return $this->hasFormFieldDescription($name) ? $this->formFieldDescriptions[$name] : null;
1671
    }
1672
1673
    /**
1674
     * Returns true if the admin has a FieldDescription with the given $name.
1675
     *
1676
     * @param string $name
1677
     *
1678
     * @return bool
1679
     */
1680
    public function hasFormFieldDescription($name)
1681
    {
1682
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1683
    }
1684
1685
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription): void
1686
    {
1687
        $this->formFieldDescriptions[$name] = $fieldDescription;
1688
    }
1689
1690
    /**
1691
     * remove a FieldDescription.
1692
     *
1693
     * @param string $name
1694
     */
1695
    public function removeFormFieldDescription($name): void
1696
    {
1697
        unset($this->formFieldDescriptions[$name]);
1698
    }
1699
1700
    /**
1701
     * build and return the collection of form FieldDescription.
1702
     *
1703
     * @return array collection of form FieldDescription
1704
     */
1705
    public function getShowFieldDescriptions()
1706
    {
1707
        $this->buildShow();
1708
1709
        return $this->showFieldDescriptions;
1710
    }
1711
1712
    /**
1713
     * Returns the form FieldDescription with the given $name.
1714
     *
1715
     * @param string $name
1716
     *
1717
     * @return FieldDescriptionInterface
1718
     */
1719
    public function getShowFieldDescription($name)
1720
    {
1721
        $this->buildShow();
1722
1723
        return $this->hasShowFieldDescription($name) ? $this->showFieldDescriptions[$name] : null;
1724
    }
1725
1726
    public function hasShowFieldDescription($name)
1727
    {
1728
        return \array_key_exists($name, $this->showFieldDescriptions);
1729
    }
1730
1731
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription): void
1732
    {
1733
        $this->showFieldDescriptions[$name] = $fieldDescription;
1734
    }
1735
1736
    public function removeShowFieldDescription($name): void
1737
    {
1738
        unset($this->showFieldDescriptions[$name]);
1739
    }
1740
1741
    public function getListFieldDescriptions()
1742
    {
1743
        $this->buildList();
1744
1745
        return $this->listFieldDescriptions;
1746
    }
1747
1748
    public function getListFieldDescription($name)
1749
    {
1750
        if (!$this->hasListFieldDescription($name)) {
1751
            @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...
1752
                'Calling %s() when there is no list field description is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
1753
                'Use %s::hasListFieldDescription(\'%s\') to know if there is a list field description.',
1754
                __METHOD__,
1755
                __CLASS__,
1756
                $name
1757
            ), E_USER_DEPRECATED);
1758
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1759
            // throw new \LogicException(sprintf(
1760
            //    'Admin "%s" has no list field description for %s.',
1761
            //    static::class,
1762
            //    $name
1763
            // ));
1764
1765
            return null;
1766
        }
1767
1768
        return $this->listFieldDescriptions[$name];
1769
    }
1770
1771
    public function hasListFieldDescription($name)
1772
    {
1773
        $this->buildList();
1774
1775
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1776
    }
1777
1778
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription): void
1779
    {
1780
        $this->listFieldDescriptions[$name] = $fieldDescription;
1781
    }
1782
1783
    public function removeListFieldDescription($name): void
1784
    {
1785
        unset($this->listFieldDescriptions[$name]);
1786
    }
1787
1788
    public function getFilterFieldDescription($name)
1789
    {
1790
        return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null;
1791
    }
1792
1793
    public function hasFilterFieldDescription($name)
1794
    {
1795
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1796
    }
1797
1798
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription): void
1799
    {
1800
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1801
    }
1802
1803
    public function removeFilterFieldDescription($name): void
1804
    {
1805
        unset($this->filterFieldDescriptions[$name]);
1806
    }
1807
1808
    public function getFilterFieldDescriptions()
1809
    {
1810
        $this->buildDatagrid();
1811
1812
        return $this->filterFieldDescriptions;
1813
    }
1814
1815
    public function addChild(AdminInterface $child): void
1816
    {
1817
        $parentAdmin = $this;
1818
        while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
1819
            $parentAdmin = $parentAdmin->getParent();
1820
        }
1821
1822
        if ($parentAdmin->getCode() === $child->getCode()) {
1823
            throw new \RuntimeException(sprintf(
1824
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1825
                $child->getCode(),
1826
                $this->getCode()
1827
            ));
1828
        }
1829
1830
        $this->children[$child->getCode()] = $child;
1831
1832
        $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...
1833
1834
        // NEXT_MAJOR: remove $args and add $field parameter to this function on next Major
1835
1836
        $args = \func_get_args();
1837
1838
        if (isset($args[1])) {
1839
            $child->addParentAssociationMapping($this->getCode(), $args[1]);
1840
        } else {
1841
            @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...
1842
                'Calling "addChild" without second argument is deprecated since'
1843
                .' sonata-project/admin-bundle 3.35 and will not be allowed in 4.0.',
1844
                E_USER_DEPRECATED
1845
            );
1846
        }
1847
    }
1848
1849
    public function hasChild($code)
1850
    {
1851
        return isset($this->children[$code]);
1852
    }
1853
1854
    public function getChildren()
1855
    {
1856
        return $this->children;
1857
    }
1858
1859
    public function getChild($code)
1860
    {
1861
        return $this->hasChild($code) ? $this->children[$code] : null;
1862
    }
1863
1864
    public function setParent(AdminInterface $parent): void
1865
    {
1866
        $this->parent = $parent;
1867
    }
1868
1869
    public function getParent()
1870
    {
1871
        if (!$this->isChild()) {
1872
            @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...
1873
                'Calling %s() when there is no parent is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
1874
                'Use %s::isChild() to know if there is a parent.',
1875
                __METHOD__,
1876
                __CLASS__
1877
            ), E_USER_DEPRECATED);
1878
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
1879
            // throw new \LogicException(sprintf(
1880
            //    'Admin "%s" has no parent.',
1881
            //    static::class
1882
            // ));
1883
1884
            return null;
1885
        }
1886
1887
        return $this->parent;
1888
    }
1889
1890
    final public function getRootAncestor()
1891
    {
1892
        $parent = $this;
1893
1894
        while ($parent->isChild()) {
1895
            $parent = $parent->getParent();
1896
        }
1897
1898
        return $parent;
1899
    }
1900
1901
    final public function getChildDepth()
1902
    {
1903
        $parent = $this;
1904
        $depth = 0;
1905
1906
        while ($parent->isChild()) {
1907
            $parent = $parent->getParent();
1908
            ++$depth;
1909
        }
1910
1911
        return $depth;
1912
    }
1913
1914
    final public function getCurrentLeafChildAdmin()
1915
    {
1916
        $child = $this->getCurrentChildAdmin();
1917
1918
        if (null === $child) {
1919
            return null;
1920
        }
1921
1922
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1923
            $child = $c;
1924
        }
1925
1926
        return $child;
1927
    }
1928
1929
    public function isChild()
1930
    {
1931
        return $this->parent instanceof AdminInterface;
1932
    }
1933
1934
    /**
1935
     * Returns true if the admin has children, false otherwise.
1936
     *
1937
     * @return bool if the admin has children
1938
     */
1939
    public function hasChildren()
1940
    {
1941
        return \count($this->children) > 0;
1942
    }
1943
1944
    public function setUniqid($uniqid): void
1945
    {
1946
        $this->uniqid = $uniqid;
1947
    }
1948
1949
    public function getUniqid()
1950
    {
1951
        if (!$this->uniqid) {
1952
            $this->uniqid = 's'.uniqid();
1953
        }
1954
1955
        return $this->uniqid;
1956
    }
1957
1958
    /**
1959
     * {@inheritdoc}
1960
     */
1961
    public function getClassnameLabel()
1962
    {
1963
        return $this->classnameLabel;
1964
    }
1965
1966
    public function getPersistentParameters()
1967
    {
1968
        $parameters = [];
1969
1970
        foreach ($this->getExtensions() as $extension) {
1971
            $params = $extension->getPersistentParameters($this);
1972
1973
            if (!\is_array($params)) {
1974
                throw new \RuntimeException(sprintf('The %s::getPersistentParameters must return an array', \get_class($extension)));
1975
            }
1976
1977
            $parameters = array_merge($parameters, $params);
1978
        }
1979
1980
        return $parameters;
1981
    }
1982
1983
    /**
1984
     * {@inheritdoc}
1985
     */
1986
    public function getPersistentParameter(string $name)
1987
    {
1988
        $parameters = $this->getPersistentParameters();
1989
1990
        return $parameters[$name] ?? null;
1991
    }
1992
1993
    public function setCurrentChild($currentChild): void
1994
    {
1995
        $this->currentChild = $currentChild;
1996
    }
1997
1998
    /**
1999
     * NEXT_MAJOR: Remove this method.
2000
     *
2001
     * @deprecated since sonata-project/admin-bundle 3.65, to be removed in 4.0
2002
     */
2003
    public function getCurrentChild()
2004
    {
2005
        @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...
2006
            sprintf(
2007
                'The %s() method is deprecated since version 3.65 and will be removed in 4.0. Use %s::isCurrentChild() instead.',
2008
                __METHOD__,
2009
                __CLASS__
2010
            ),
2011
            E_USER_DEPRECATED
2012
        );
2013
2014
        return $this->currentChild;
2015
    }
2016
2017
    public function isCurrentChild(): bool
2018
    {
2019
        return $this->currentChild;
2020
    }
2021
2022
    /**
2023
     * Returns the current child admin instance.
2024
     *
2025
     * @return AdminInterface|null the current child admin instance
2026
     */
2027
    public function getCurrentChildAdmin()
2028
    {
2029
        foreach ($this->children as $children) {
2030
            if ($children->isCurrentChild()) {
2031
                return $children;
2032
            }
2033
        }
2034
2035
        return null;
2036
    }
2037
2038
    public function trans($id, array $parameters = [], $domain = null, $locale = null)
2039
    {
2040
        @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...
2041
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2042
            E_USER_DEPRECATED
2043
        );
2044
2045
        $domain = $domain ?: $this->getTranslationDomain();
2046
2047
        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...
2048
    }
2049
2050
    /**
2051
     * Translate a message id.
2052
     *
2053
     * NEXT_MAJOR: remove this method
2054
     *
2055
     * @param string      $id
2056
     * @param int         $count
2057
     * @param string|null $domain
2058
     * @param string|null $locale
2059
     *
2060
     * @return string the translated string
2061
     *
2062
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2063
     */
2064
    public function transChoice($id, $count, array $parameters = [], $domain = null, $locale = null)
2065
    {
2066
        @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...
2067
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2068
            E_USER_DEPRECATED
2069
        );
2070
2071
        $domain = $domain ?: $this->getTranslationDomain();
2072
2073
        return $this->translator->transChoice($id, $count, $parameters, $domain, $locale);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since sonata-project/admin-bundle 3.9, to be removed with 4.0

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

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

Loading history...
2074
    }
2075
2076
    public function setTranslationDomain($translationDomain): void
2077
    {
2078
        $this->translationDomain = $translationDomain;
2079
    }
2080
2081
    public function getTranslationDomain()
2082
    {
2083
        return $this->translationDomain;
2084
    }
2085
2086
    /**
2087
     * {@inheritdoc}
2088
     *
2089
     * NEXT_MAJOR: remove this method
2090
     *
2091
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2092
     */
2093
    public function setTranslator(TranslatorInterface $translator): void
2094
    {
2095
        $args = \func_get_args();
2096
        if (isset($args[1]) && $args[1]) {
2097
            @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...
2098
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2099
                E_USER_DEPRECATED
2100
            );
2101
        }
2102
2103
        $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...
2104
    }
2105
2106
    /**
2107
     * {@inheritdoc}
2108
     *
2109
     * NEXT_MAJOR: remove this method
2110
     *
2111
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2112
     */
2113
    public function getTranslator()
2114
    {
2115
        @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...
2116
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2117
            E_USER_DEPRECATED
2118
        );
2119
2120
        return $this->translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since sonata-project/admin-bundle 3.9, to be removed with 4.0

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

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

Loading history...
2121
    }
2122
2123
    public function getTranslationLabel($label, $context = '', $type = '')
2124
    {
2125
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2126
    }
2127
2128
    public function setRequest(Request $request): void
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...
2129
    {
2130
        $this->request = $request;
2131
2132
        foreach ($this->getChildren() as $children) {
2133
            $children->setRequest($request);
2134
        }
2135
    }
2136
2137
    public function getRequest()
2138
    {
2139
        if (!$this->request) {
2140
            // NEXT_MAJOR: Throw \LogicException instead.
2141
            throw new \RuntimeException('The Request object has not been set');
2142
        }
2143
2144
        return $this->request;
2145
    }
2146
2147
    public function hasRequest()
2148
    {
2149
        return null !== $this->request;
2150
    }
2151
2152
    public function setFormContractor(FormContractorInterface $formBuilder): void
2153
    {
2154
        $this->formContractor = $formBuilder;
2155
    }
2156
2157
    /**
2158
     * @return FormContractorInterface
2159
     */
2160
    public function getFormContractor()
2161
    {
2162
        return $this->formContractor;
2163
    }
2164
2165
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder): void
2166
    {
2167
        $this->datagridBuilder = $datagridBuilder;
2168
    }
2169
2170
    public function getDatagridBuilder()
2171
    {
2172
        return $this->datagridBuilder;
2173
    }
2174
2175
    public function setListBuilder(ListBuilderInterface $listBuilder): void
2176
    {
2177
        $this->listBuilder = $listBuilder;
2178
    }
2179
2180
    public function getListBuilder()
2181
    {
2182
        return $this->listBuilder;
2183
    }
2184
2185
    public function setShowBuilder(ShowBuilderInterface $showBuilder): void
2186
    {
2187
        $this->showBuilder = $showBuilder;
2188
    }
2189
2190
    /**
2191
     * @return ShowBuilderInterface
2192
     */
2193
    public function getShowBuilder()
2194
    {
2195
        return $this->showBuilder;
2196
    }
2197
2198
    public function setConfigurationPool(Pool $configurationPool): void
2199
    {
2200
        $this->configurationPool = $configurationPool;
2201
    }
2202
2203
    /**
2204
     * @return Pool
2205
     */
2206
    public function getConfigurationPool()
2207
    {
2208
        return $this->configurationPool;
2209
    }
2210
2211
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator): void
2212
    {
2213
        $this->routeGenerator = $routeGenerator;
2214
    }
2215
2216
    /**
2217
     * @return RouteGeneratorInterface
2218
     */
2219
    public function getRouteGenerator()
2220
    {
2221
        return $this->routeGenerator;
2222
    }
2223
2224
    public function getCode()
2225
    {
2226
        return $this->code;
2227
    }
2228
2229
    public function getBaseCodeRoute()
2230
    {
2231
        if ($this->isChild()) {
2232
            return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2233
        }
2234
2235
        return $this->getCode();
2236
    }
2237
2238
    public function getModelManager()
2239
    {
2240
        return $this->modelManager;
2241
    }
2242
2243
    public function setModelManager(ModelManagerInterface $modelManager): void
2244
    {
2245
        $this->modelManager = $modelManager;
2246
    }
2247
2248
    public function getManagerType()
2249
    {
2250
        return $this->managerType;
2251
    }
2252
2253
    /**
2254
     * @param string $type
2255
     */
2256
    public function setManagerType($type): void
2257
    {
2258
        $this->managerType = $type;
2259
    }
2260
2261
    public function getObjectIdentifier()
2262
    {
2263
        return $this->getCode();
2264
    }
2265
2266
    /**
2267
     * Set the roles and permissions per role.
2268
     */
2269
    public function setSecurityInformation(array $information): void
2270
    {
2271
        $this->securityInformation = $information;
2272
    }
2273
2274
    public function getSecurityInformation()
2275
    {
2276
        return $this->securityInformation;
2277
    }
2278
2279
    /**
2280
     * Return the list of permissions the user should have in order to display the admin.
2281
     *
2282
     * @param string $context
2283
     *
2284
     * @return array
2285
     */
2286
    public function getPermissionsShow($context)
2287
    {
2288
        switch ($context) {
2289
            case self::CONTEXT_DASHBOARD:
2290
            case self::CONTEXT_MENU:
2291
            default:
2292
                return ['LIST'];
2293
        }
2294
    }
2295
2296
    public function showIn($context)
2297
    {
2298
        switch ($context) {
2299
            case self::CONTEXT_DASHBOARD:
2300
            case self::CONTEXT_MENU:
2301
            default:
2302
                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...
2303
        }
2304
    }
2305
2306
    public function createObjectSecurity($object): void
2307
    {
2308
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2309
    }
2310
2311
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler): void
2312
    {
2313
        $this->securityHandler = $securityHandler;
2314
    }
2315
2316
    public function getSecurityHandler()
2317
    {
2318
        return $this->securityHandler;
2319
    }
2320
2321
    public function isGranted($name, $object = null)
2322
    {
2323
        $objectRef = $object ? '/'.spl_object_hash($object).'#'.$this->id($object) : '';
2324
        $key = md5(json_encode($name).$objectRef);
2325
2326
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
2327
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2328
        }
2329
2330
        return $this->cacheIsGranted[$key];
2331
    }
2332
2333
    public function getUrlSafeIdentifier($entity)
2334
    {
2335
        return $this->getModelManager()->getUrlSafeIdentifier($entity);
2336
    }
2337
2338
    public function getNormalizedIdentifier($entity)
2339
    {
2340
        return $this->getModelManager()->getNormalizedIdentifier($entity);
2341
    }
2342
2343
    public function id($entity)
2344
    {
2345
        return $this->getNormalizedIdentifier($entity);
2346
    }
2347
2348
    public function setValidator(ValidatorInterface $validator): void
2349
    {
2350
        $this->validator = $validator;
2351
    }
2352
2353
    public function getValidator()
2354
    {
2355
        return $this->validator;
2356
    }
2357
2358
    public function getShow()
2359
    {
2360
        $this->buildShow();
2361
2362
        return $this->show;
2363
    }
2364
2365
    public function setFormTheme(array $formTheme): void
2366
    {
2367
        $this->formTheme = $formTheme;
2368
    }
2369
2370
    public function getFormTheme()
2371
    {
2372
        return $this->formTheme;
2373
    }
2374
2375
    public function setFilterTheme(array $filterTheme): void
2376
    {
2377
        $this->filterTheme = $filterTheme;
2378
    }
2379
2380
    public function getFilterTheme()
2381
    {
2382
        return $this->filterTheme;
2383
    }
2384
2385
    public function addExtension(AdminExtensionInterface $extension): void
2386
    {
2387
        $this->extensions[] = $extension;
2388
    }
2389
2390
    public function getExtensions()
2391
    {
2392
        return $this->extensions;
2393
    }
2394
2395
    public function setMenuFactory(FactoryInterface $menuFactory): void
2396
    {
2397
        $this->menuFactory = $menuFactory;
2398
    }
2399
2400
    public function getMenuFactory()
2401
    {
2402
        return $this->menuFactory;
2403
    }
2404
2405
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder): void
2406
    {
2407
        $this->routeBuilder = $routeBuilder;
2408
    }
2409
2410
    public function getRouteBuilder()
2411
    {
2412
        return $this->routeBuilder;
2413
    }
2414
2415
    public function toString($object)
2416
    {
2417
        if (!\is_object($object)) {
2418
            return '';
2419
        }
2420
2421
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2422
            return (string) $object;
2423
        }
2424
2425
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2426
    }
2427
2428
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy): void
2429
    {
2430
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2431
    }
2432
2433
    public function getLabelTranslatorStrategy()
2434
    {
2435
        return $this->labelTranslatorStrategy;
2436
    }
2437
2438
    public function supportsPreviewMode()
2439
    {
2440
        return $this->supportsPreviewMode;
2441
    }
2442
2443
    /**
2444
     * NEXT_MAJOR: Remove this.
2445
     *
2446
     * @deprecated since sonata-project/admin-bundle 3.x, to be removed in 4.0.
2447
     *
2448
     * Set custom per page options.
2449
     */
2450
    public function setPerPageOptions(array $options): void
2451
    {
2452
        @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...
2453
            'The method %s is deprecated since sonata-project/admin-bundle 3.x and will be removed in 4.0.',
2454
            __METHOD__
2455
        ), E_USER_DEPRECATED);
2456
2457
        $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.x.

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

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

Loading history...
2458
    }
2459
2460
    /**
2461
     * Returns predefined per page options.
2462
     *
2463
     * @return array
2464
     */
2465
    public function getPerPageOptions()
2466
    {
2467
        // NEXT_MAJOR: Remove this line and uncomment the following
2468
        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.x.

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...
2469
//        $perPageOptions = $this->getModelManager()->getDefaultPerPageOptions($this->class);
2470
//        $perPageOptions[] = $this->getMaxPerPage();
2471
//
2472
//        $perPageOptions = array_unique($perPageOptions);
2473
//        sort($perPageOptions);
2474
//
2475
//        return $perPageOptions;
2476
    }
2477
2478
    /**
2479
     * Set pager type.
2480
     *
2481
     * @param string $pagerType
2482
     */
2483
    public function setPagerType($pagerType): void
2484
    {
2485
        $this->pagerType = $pagerType;
2486
    }
2487
2488
    /**
2489
     * Get pager type.
2490
     *
2491
     * @return string
2492
     */
2493
    public function getPagerType()
2494
    {
2495
        return $this->pagerType;
2496
    }
2497
2498
    /**
2499
     * Returns true if the per page value is allowed, false otherwise.
2500
     *
2501
     * @param int $perPage
2502
     *
2503
     * @return bool
2504
     */
2505
    public function determinedPerPageValue($perPage)
2506
    {
2507
        return \in_array($perPage, $this->getPerPageOptions(), true);
2508
    }
2509
2510
    public function isAclEnabled()
2511
    {
2512
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2513
    }
2514
2515
    public function getObjectMetadata($object)
2516
    {
2517
        return new Metadata($this->toString($object));
2518
    }
2519
2520
    public function getListModes()
2521
    {
2522
        return $this->listModes;
2523
    }
2524
2525
    public function setListMode($mode): void
2526
    {
2527
        if (!$this->hasRequest()) {
2528
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2529
        }
2530
2531
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2532
    }
2533
2534
    public function getListMode()
2535
    {
2536
        if (!$this->hasRequest()) {
2537
            return 'list';
2538
        }
2539
2540
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2541
    }
2542
2543
    public function getAccessMapping()
2544
    {
2545
        return $this->accessMapping;
2546
    }
2547
2548
    public function checkAccess($action, $object = null): void
2549
    {
2550
        $access = $this->getAccess();
2551
2552
        if (!\array_key_exists($action, $access)) {
2553
            throw new \InvalidArgumentException(sprintf(
2554
                'Action "%s" could not be found in access mapping.'
2555
                .' Please make sure your action is defined into your admin class accessMapping property.',
2556
                $action
2557
            ));
2558
        }
2559
2560
        if (!\is_array($access[$action])) {
2561
            $access[$action] = [$access[$action]];
2562
        }
2563
2564
        foreach ($access[$action] as $role) {
2565
            if (false === $this->isGranted($role, $object)) {
2566
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2567
            }
2568
        }
2569
    }
2570
2571
    /**
2572
     * {@inheritdoc}
2573
     */
2574
    public function hasAccess(string $action, ?object $object = null): bool
2575
    {
2576
        $access = $this->getAccess();
2577
2578
        if (!\array_key_exists($action, $access)) {
2579
            return false;
2580
        }
2581
2582
        if (!\is_array($access[$action])) {
2583
            $access[$action] = [$access[$action]];
2584
        }
2585
2586
        foreach ($access[$action] as $role) {
2587
            if (false === $this->isGranted($role, $object)) {
2588
                return false;
2589
            }
2590
        }
2591
2592
        return true;
2593
    }
2594
2595
    /**
2596
     * @param object|null $object
2597
     */
2598
    final public function getActionButtons(string $action, $object = null): array
2599
    {
2600
        $buttonList = [];
2601
2602
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
2603
            && $this->hasAccess('create')
2604
            && $this->hasRoute('create')
2605
        ) {
2606
            $buttonList['create'] = [
2607
                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2608
            ];
2609
        }
2610
2611
        if (\in_array($action, ['show', 'delete', 'acl', 'history'], true)
2612
            && $this->canAccessObject('edit', $object)
0 ignored issues
show
Documentation introduced by
$object is of type object|null, but the function expects a object<Sonata\AdminBundle\Admin\object>.

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...
2613
            && $this->hasRoute('edit')
2614
        ) {
2615
            $buttonList['edit'] = [
2616
                'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2617
            ];
2618
        }
2619
2620
        if (\in_array($action, ['show', 'edit', 'acl'], true)
2621
            && $this->canAccessObject('history', $object)
0 ignored issues
show
Documentation introduced by
$object is of type object|null, but the function expects a object<Sonata\AdminBundle\Admin\object>.

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...
2622
            && $this->hasRoute('history')
2623
        ) {
2624
            $buttonList['history'] = [
2625
                'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2626
            ];
2627
        }
2628
2629
        if (\in_array($action, ['edit', 'history'], true)
2630
            && $this->isAclEnabled()
2631
            && $this->canAccessObject('acl', $object)
0 ignored issues
show
Documentation introduced by
$object is of type object|null, but the function expects a object<Sonata\AdminBundle\Admin\object>.

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...
2632
            && $this->hasRoute('acl')
2633
        ) {
2634
            $buttonList['acl'] = [
2635
                'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2636
            ];
2637
        }
2638
2639
        if (\in_array($action, ['edit', 'history', 'acl'], true)
2640
            && $this->canAccessObject('show', $object)
0 ignored issues
show
Documentation introduced by
$object is of type object|null, but the function expects a object<Sonata\AdminBundle\Admin\object>.

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...
2641
            && \count($this->getShow()) > 0
2642
            && $this->hasRoute('show')
2643
        ) {
2644
            $buttonList['show'] = [
2645
                'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2646
            ];
2647
        }
2648
2649
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
2650
            && $this->hasAccess('list')
2651
            && $this->hasRoute('list')
2652
        ) {
2653
            $buttonList['list'] = [
2654
                'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2655
            ];
2656
        }
2657
2658
        $buttonList = $this->configureActionButtons($buttonList, $action, $object);
2659
2660
        foreach ($this->getExtensions() as $extension) {
2661
            $buttonList = $extension->configureActionButtons($this, $buttonList, $action, $object);
0 ignored issues
show
Bug introduced by
It seems like $object defined by parameter $object on line 2598 can also be of type null; however, Sonata\AdminBundle\Admin...onfigureActionButtons() 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...
2662
        }
2663
2664
        return $buttonList;
2665
    }
2666
2667
    /**
2668
     * {@inheritdoc}
2669
     */
2670
    public function getDashboardActions()
2671
    {
2672
        $actions = [];
2673
2674
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2675
            $actions['create'] = [
2676
                'label' => 'link_add',
2677
                'translation_domain' => 'SonataAdminBundle',
2678
                'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2679
                'url' => $this->generateUrl('create'),
2680
                'icon' => 'plus-circle',
2681
            ];
2682
        }
2683
2684
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2685
            $actions['list'] = [
2686
                'label' => 'link_list',
2687
                'translation_domain' => 'SonataAdminBundle',
2688
                'url' => $this->generateUrl('list'),
2689
                'icon' => 'list',
2690
            ];
2691
        }
2692
2693
        return $actions;
2694
    }
2695
2696
    /**
2697
     * {@inheritdoc}
2698
     */
2699
    final public function showMosaicButton($isShown): void
2700
    {
2701
        if ($isShown) {
2702
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
2703
        } else {
2704
            unset($this->listModes['mosaic']);
2705
        }
2706
    }
2707
2708
    /**
2709
     * @param object $object
2710
     */
2711
    final public function getSearchResultLink($object): ?string
2712
    {
2713
        foreach ($this->searchResultActions as $action) {
2714
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2715
                return $this->generateObjectUrl($action, $object);
2716
            }
2717
        }
2718
2719
        return null;
2720
    }
2721
2722
    /**
2723
     * Checks if a filter type is set to a default value.
2724
     */
2725
    final public function isDefaultFilter(string $name): bool
2726
    {
2727
        $filter = $this->getFilterParameters();
2728
        $default = $this->getDefaultFilterValues();
2729
2730
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
2731
            return false;
2732
        }
2733
2734
        return $filter[$name] === $default[$name];
2735
    }
2736
2737
    public function canAccessObject(string $action, object $object): bool
2738
    {
2739
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2740
    }
2741
2742
    public function configureActionButtons(array $buttonList, string $action, ?object $object = null): array
2743
    {
2744
        return $buttonList;
2745
    }
2746
2747
    /**
2748
     * Hook to run after initilization.
2749
     */
2750
    protected function configure(): void
2751
    {
2752
    }
2753
2754
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
2755
    {
2756
        return $query;
2757
    }
2758
2759
    /**
2760
     * urlize the given word.
2761
     *
2762
     * @param string $word
2763
     * @param string $sep  the separator
2764
     *
2765
     * @return string
2766
     */
2767
    final protected function urlize($word, $sep = '_')
2768
    {
2769
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
2770
    }
2771
2772
    final protected function getTemplateRegistry(): MutableTemplateRegistryInterface
2773
    {
2774
        return $this->templateRegistry;
2775
    }
2776
2777
    /**
2778
     * Returns a list of default sort values.
2779
     *
2780
     * @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...
2781
     */
2782
    final protected function getDefaultSortValues(): array
2783
    {
2784
        $defaultSortValues = [];
2785
2786
        $this->configureDefaultSortValues($defaultSortValues);
2787
2788
        foreach ($this->getExtensions() as $extension) {
2789
            // NEXT_MAJOR: remove method check
2790
            if (method_exists($extension, 'configureDefaultSortValues')) {
2791
                $extension->configureDefaultSortValues($this, $defaultSortValues);
2792
            }
2793
        }
2794
2795
        return $defaultSortValues;
2796
    }
2797
2798
    /**
2799
     * Returns a list of default filters.
2800
     *
2801
     * @return array
2802
     */
2803
    final protected function getDefaultFilterValues()
2804
    {
2805
        $defaultFilterValues = [];
2806
2807
        $this->configureDefaultFilterValues($defaultFilterValues);
2808
2809
        foreach ($this->getExtensions() as $extension) {
2810
            // NEXT_MAJOR: remove method check
2811
            if (method_exists($extension, 'configureDefaultFilterValues')) {
2812
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2813
            }
2814
        }
2815
2816
        return $defaultFilterValues;
2817
    }
2818
2819
    protected function configureFormFields(FormMapper $form): void
2820
    {
2821
    }
2822
2823
    protected function configureListFields(ListMapper $list): void
2824
    {
2825
    }
2826
2827
    protected function configureDatagridFilters(DatagridMapper $filter): void
2828
    {
2829
    }
2830
2831
    protected function configureShowFields(ShowMapper $show): void
2832
    {
2833
    }
2834
2835
    protected function configureRoutes(RouteCollection $collection): void
2836
    {
2837
    }
2838
2839
    /**
2840
     * Allows you to customize batch actions.
2841
     *
2842
     * @param array $actions List of actions
2843
     *
2844
     * @return array
2845
     */
2846
    protected function configureBatchActions($actions)
2847
    {
2848
        return $actions;
2849
    }
2850
2851
    /**
2852
     * NEXT_MAJOR: remove this method.
2853
     *
2854
     * @deprecated Use configureTabMenu instead
2855
     */
2856
    protected function configureSideMenu(ItemInterface $menu, $action, ?AdminInterface $childAdmin = null)
0 ignored issues
show
Unused Code introduced by
The parameter $menu is not used and could be removed.

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

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

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

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

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

Loading history...
2857
    {
2858
    }
2859
2860
    /**
2861
     * Configures the tab menu in your admin.
2862
     *
2863
     * @param string $action
2864
     */
2865
    protected function configureTabMenu(ItemInterface $menu, $action, ?AdminInterface $childAdmin = null)
2866
    {
2867
        // Use configureSideMenu not to mess with previous overrides
2868
        // NEXT_MAJOR: remove this line
2869
        $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...
2870
    }
2871
2872
    /**
2873
     * build the view FieldDescription array.
2874
     */
2875
    protected function buildShow(): void
2876
    {
2877
        if ($this->show) {
2878
            return;
2879
        }
2880
2881
        $this->show = new FieldDescriptionCollection();
2882
        $mapper = new ShowMapper($this->showBuilder, $this->show, $this);
2883
2884
        $this->configureShowFields($mapper);
2885
2886
        foreach ($this->getExtensions() as $extension) {
2887
            $extension->configureShowFields($mapper);
2888
        }
2889
    }
2890
2891
    /**
2892
     * build the list FieldDescription array.
2893
     */
2894
    protected function buildList(): void
2895
    {
2896
        if ($this->list) {
2897
            return;
2898
        }
2899
2900
        $this->list = $this->getListBuilder()->getBaseList();
2901
2902
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
2903
2904
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
2905
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2906
                $this->getClass(),
2907
                'batch',
2908
                [
2909
                    'label' => 'batch',
2910
                    'code' => '_batch',
2911
                    'sortable' => false,
2912
                    'virtual_field' => true,
2913
                ]
2914
            );
2915
2916
            $fieldDescription->setAdmin($this);
2917
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
2918
2919
            $mapper->add($fieldDescription, 'batch');
2920
        }
2921
2922
        $this->configureListFields($mapper);
2923
2924
        foreach ($this->getExtensions() as $extension) {
2925
            $extension->configureListFields($mapper);
2926
        }
2927
2928
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
2929
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2930
                $this->getClass(),
2931
                'select',
2932
                [
2933
                    'label' => false,
2934
                    'code' => '_select',
2935
                    'sortable' => false,
2936
                    'virtual_field' => false,
2937
                ]
2938
            );
2939
2940
            $fieldDescription->setAdmin($this);
2941
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
2942
2943
            $mapper->add($fieldDescription, 'select');
2944
        }
2945
    }
2946
2947
    /**
2948
     * Build the form FieldDescription collection.
2949
     */
2950
    protected function buildForm(): void
2951
    {
2952
        if ($this->form) {
2953
            return;
2954
        }
2955
2956
        // append parent object if any
2957
        // todo : clean the way the Admin class can retrieve set the object
2958
        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...
2959
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
2960
2961
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
2962
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
2963
2964
            $object = $this->getSubject();
2965
2966
            $value = $propertyAccessor->getValue($object, $propertyPath);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 2964 can also be of type null; however, Symfony\Component\Proper...orInterface::getValue() does only seem to accept object|array, maybe add an additional type check?

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

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

    return array();
}

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

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

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

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

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

    return array();
}

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

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

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

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

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

    return array();
}

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

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

Loading history...
2973
            }
2974
        }
2975
2976
        $formBuilder = $this->getFormBuilder();
2977
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
2978
            $this->preValidate($event->getData());
2979
        }, 100);
2980
2981
        $this->form = $formBuilder->getForm();
0 ignored issues
show
Documentation Bug introduced by
It seems like $formBuilder->getForm() of type object<Symfony\Component\Form\FormInterface> is incompatible with the declared type object<Symfony\Component\Form\Form>|null of property $form.

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

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

Loading history...
2982
    }
2983
2984
    /**
2985
     * Gets the subclass corresponding to the given name.
2986
     *
2987
     * @param string $name The name of the sub class
2988
     *
2989
     * @return string the subclass
2990
     */
2991
    protected function getSubClass($name)
2992
    {
2993
        if ($this->hasSubClass($name)) {
2994
            return $this->subClasses[$name];
2995
        }
2996
2997
        // NEXT_MAJOR: Throw \LogicException instead.
2998
        throw new \RuntimeException(sprintf(
2999
            'Unable to find the subclass `%s` for admin `%s`',
3000
            $name,
3001
            static::class
3002
        ));
3003
    }
3004
3005
    /**
3006
     * Attach the inline validator to the model metadata, this must be done once per admin.
3007
     */
3008
    protected function attachInlineValidator(): void
3009
    {
3010
        $admin = $this;
3011
3012
        // add the custom inline validation option
3013
        $metadata = $this->validator->getMetadataFor($this->getClass());
3014
3015
        $metadata->addConstraint(new InlineConstraint([
3016
            'service' => $this,
3017
            'method' => static function (ErrorElement $errorElement, $object) use ($admin): void {
3018
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
3019
3020
                // This avoid the main validation to be cascaded to children
3021
                // The problem occurs when a model Page has a collection of Page as property
3022
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
3023
                    return;
3024
                }
3025
3026
                $admin->validate($errorElement, $object);
3027
3028
                foreach ($admin->getExtensions() as $extension) {
3029
                    $extension->validate($admin, $errorElement, $object);
3030
                }
3031
            },
3032
            'serializingWarning' => true,
3033
        ]));
3034
    }
3035
3036
    /**
3037
     * NEXT_MAJOR: Remove this function.
3038
     *
3039
     * @deprecated since sonata-project/admin-bundle 3.x, to be removed in 4.0.
3040
     *
3041
     * Predefine per page options.
3042
     */
3043
    protected function predefinePerPageOptions(): void
3044
    {
3045
        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.x.

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.x.

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...
3046
        $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.x.

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...
3047
        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.x.

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...
3048
    }
3049
3050
    /**
3051
     * Return list routes with permissions name.
3052
     *
3053
     * @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...
3054
     */
3055
    protected function getAccess()
3056
    {
3057
        $access = array_merge([
3058
            'acl' => 'MASTER',
3059
            'export' => 'EXPORT',
3060
            'historyCompareRevisions' => 'EDIT',
3061
            'historyViewRevision' => 'EDIT',
3062
            'history' => 'EDIT',
3063
            'edit' => 'EDIT',
3064
            'show' => 'VIEW',
3065
            'create' => 'CREATE',
3066
            'delete' => 'DELETE',
3067
            'batchDelete' => 'DELETE',
3068
            'list' => 'LIST',
3069
        ], $this->getAccessMapping());
3070
3071
        foreach ($this->extensions as $extension) {
3072
            $access = array_merge($access, $extension->getAccessMapping($this));
3073
        }
3074
3075
        return $access;
3076
    }
3077
3078
    /**
3079
     * Configures a list of default filters.
3080
     */
3081
    protected function configureDefaultFilterValues(array &$filterValues): void
3082
    {
3083
    }
3084
3085
    /**
3086
     * Configures a list of default sort values.
3087
     *
3088
     * Example:
3089
     *   $sortValues['_sort_by'] = 'foo'
3090
     *   $sortValues['_sort_order'] = 'DESC'
3091
     */
3092
    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...
3093
    {
3094
    }
3095
3096
    /**
3097
     * {@inheritdoc}
3098
     */
3099
    private function buildDatagrid(): void
3100
    {
3101
        if ($this->datagrid) {
3102
            return;
3103
        }
3104
3105
        $filterParameters = $this->getFilterParameters();
3106
3107
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
3108
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
3109
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
3110
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
3111
            } else {
3112
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
3113
                    $this->getClass(),
3114
                    $filterParameters['_sort_by'],
3115
                    []
3116
                );
3117
3118
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
3119
            }
3120
        }
3121
3122
        // initialize the datagrid
3123
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
3124
3125
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
3126
3127
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
3128
3129
        // build the datagrid filter
3130
        $this->configureDatagridFilters($mapper);
3131
3132
        // ok, try to limit to add parent filter
3133
        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...
3134
            $mapper->add($this->getParentAssociationMapping(), null, [
3135
                'show_filter' => false,
3136
                'label' => false,
3137
                'field_type' => ModelHiddenType::class,
3138
                'field_options' => [
3139
                    'model_manager' => $this->getModelManager(),
3140
                ],
3141
                'operator_type' => HiddenType::class,
3142
            ], null, null, [
3143
                'admin_code' => $this->getParent()->getCode(),
3144
            ]);
3145
        }
3146
3147
        foreach ($this->getExtensions() as $extension) {
3148
            $extension->configureDatagridFilters($mapper);
3149
        }
3150
    }
3151
3152
    /**
3153
     * Build all the related urls to the current admin.
3154
     */
3155
    private function buildRoutes(): void
3156
    {
3157
        if ($this->loaded['routes']) {
3158
            return;
3159
        }
3160
3161
        $this->loaded['routes'] = true;
3162
3163
        $this->routes = new RouteCollection(
3164
            $this->getBaseCodeRoute(),
3165
            $this->getBaseRouteName(),
3166
            $this->getBaseRoutePattern(),
3167
            $this->getBaseControllerName()
3168
        );
3169
3170
        $this->routeBuilder->build($this, $this->routes);
3171
3172
        $this->configureRoutes($this->routes);
3173
3174
        foreach ($this->getExtensions() as $extension) {
3175
            $extension->configureRoutes($this, $this->routes);
3176
        }
3177
    }
3178
}
3179
3180
class_exists(\Sonata\Form\Validator\ErrorElement::class);
3181