Completed
Push — 3.x ( 501e38...26e64c )
by Vincent
03:02
created

AbstractAdmin   F

Complexity

Total Complexity 452

Size/Duplication

Total Lines 3368
Duplicated Lines 0 %

Coupling/Cohesion

Components 7
Dependencies 39

Importance

Changes 0
Metric Value
wmc 452
lcom 7
cbo 39
dl 0
loc 3368
rs 0.8
c 0
b 0
f 0

232 Methods

Rating   Name   Duplication   Size   Complexity  
A setBaseControllerName() 0 4 1
A getBaseControllerName() 0 4 1
A setLabel() 0 4 1
A getLabel() 0 4 1
A setPersistFilters() 0 9 1
A buildSideMenu() 0 4 1
A getSideMenu() 0 10 2
A getRootCode() 0 4 1
A getForm() 0 6 1
A getList() 0 6 1
A __construct() 0 30 5
A getExportFormats() 0 6 1
A getExportFields() 0 12 3
A getDataSourceIterator() 0 22 3
A validate() 0 3 1
A initialize() 0 16 2
A configure() 0 3 1
A update() 0 20 4
A create() 0 22 4
A delete() 0 15 3
A preValidate() 0 3 1
A preUpdate() 0 3 1
A postUpdate() 0 3 1
A prePersist() 0 3 1
A postPersist() 0 3 1
A preRemove() 0 3 1
A postRemove() 0 3 1
A preBatchAction() 0 3 1
C getFilterParameters() 0 52 11
B buildDatagrid() 0 52 9
A getParentAssociationMapping() 0 20 4
A addParentAssociationMapping() 0 4 1
B getBaseRoutePattern() 0 42 8
B getBaseRouteName() 0 41 8
A urlize() 0 4 1
B getClass() 0 23 6
A getSubClasses() 0 4 1
A addSubClass() 0 11 2
A setSubClasses() 0 4 1
A hasSubClass() 0 4 1
A hasActiveSubClass() 0 8 3
A getActiveSubClass() 0 20 2
A getActiveSubclassCode() 0 38 3
B getBatchActions() 0 33 8
A getRoutes() 0 6 1
A getRouterIdParameter() 0 4 1
A getIdParameter() 0 10 2
A hasRoute() 0 8 2
A isCurrentRoute() 0 21 4
A generateObjectUrl() 0 6 1
A generateUrl() 0 4 1
A generateMenuUrl() 0 4 1
A setTemplateRegistry() 0 4 1
A setTemplates() 0 7 1
A setTemplate() 0 7 1
A getTemplates() 0 4 1
A getTemplate() 0 4 1
A getNewInstance() 0 9 2
A getFormBuilder() 0 13 1
A defineFormBuilder() 0 26 3
A attachAdminClass() 0 22 4
A getObject() 0 9 2
A createQuery() 0 18 3
A getDatagrid() 0 6 1
A buildTabMenu() 0 27 4
A getRoot() 0 8 2
A setFilterPersister() 0 6 1
A setMaxPerPage() 0 9 1
A getMaxPerPage() 0 8 1
A setMaxPageLinks() 0 4 1
A getMaxPageLinks() 0 4 1
A getFormGroups() 0 11 3
A setFormGroups() 0 4 1
A removeFieldFromFormGroup() 0 10 3
A reorderFormGroup() 0 7 1
A getFormTabs() 0 11 3
A setFormTabs() 0 4 1
A getShowTabs() 0 11 3
A setShowTabs() 0 4 1
A getShowGroups() 0 11 3
A setShowGroups() 0 4 1
A reorderShowGroup() 0 7 1
A setParentFieldDescription() 0 4 1
A getParentFieldDescription() 0 20 2
A hasParentFieldDescription() 0 4 1
A setSubject() 0 17 3
A getSubject() 0 20 2
A hasSubject() 0 12 5
A getFormFieldDescriptions() 0 6 1
A getFormFieldDescription() 0 4 2
A hasFormFieldDescription() 0 4 2
A addFormFieldDescription() 0 4 1
A removeFormFieldDescription() 0 4 1
A getShowFieldDescriptions() 0 6 1
A getShowFieldDescription() 0 6 2
A hasShowFieldDescription() 0 4 1
A addShowFieldDescription() 0 4 1
A removeShowFieldDescription() 0 4 1
A getListFieldDescriptions() 0 6 1
A getListFieldDescription() 0 22 2
A hasListFieldDescription() 0 6 2
A addListFieldDescription() 0 4 1
A removeListFieldDescription() 0 4 1
A getFilterFieldDescription() 0 4 2
A hasFilterFieldDescription() 0 4 2
A addFilterFieldDescription() 0 4 1
A removeFilterFieldDescription() 0 4 1
A getFilterFieldDescriptions() 0 6 1
A addChild() 0 33 5
A hasChild() 0 4 1
A getChildren() 0 4 1
A getChild() 0 4 2
A setParent() 0 4 1
A getParent() 0 20 2
A getRootAncestor() 0 10 2
A getChildDepth() 0 12 2
A getCurrentLeafChildAdmin() 0 14 3
A isChild() 0 4 1
A hasChildren() 0 4 1
A setUniqid() 0 4 1
A getUniqid() 0 8 2
A getClassnameLabel() 0 4 1
A getPersistentParameters() 0 16 3
A getPersistentParameter() 0 6 1
A getBreadcrumbs() 0 10 1
A buildBreadcrumbs() 0 14 2
A getBreadcrumbsBuilder() 0 15 2
A setBreadcrumbsBuilder() 0 11 1
A setCurrentChild() 0 4 1
A getCurrentChild() 0 13 1
A isCurrentChild() 0 4 1
A getCurrentChildAdmin() 0 10 3
A trans() 0 11 2
A transChoice() 0 11 2
A setTranslationDomain() 0 4 1
A getTranslationDomain() 0 4 1
A setTranslator() 0 12 3
A getTranslator() 0 9 1
A getTranslationLabel() 0 4 1
A setRequest() 0 8 2
A getRequest() 0 9 2
A hasRequest() 0 4 1
A setFormContractor() 0 4 1
A getFormContractor() 0 4 1
A setDatagridBuilder() 0 4 1
A getDatagridBuilder() 0 4 1
A setListBuilder() 0 4 1
A getListBuilder() 0 4 1
A setShowBuilder() 0 4 1
A getShowBuilder() 0 4 1
A setConfigurationPool() 0 4 1
A getConfigurationPool() 0 4 1
A setRouteGenerator() 0 4 1
A getRouteGenerator() 0 4 1
A getCode() 0 4 1
A setBaseCodeRoute() 0 9 1
A getBaseCodeRoute() 0 22 3
A getModelManager() 0 4 1
A setModelManager() 0 4 1
A getManagerType() 0 4 1
A setManagerType() 0 4 1
A getObjectIdentifier() 0 4 1
A setSecurityInformation() 0 4 1
A getSecurityInformation() 0 4 1
A getPermissionsShow() 0 9 3
A showIn() 0 9 3
A createObjectSecurity() 0 4 1
A setSecurityHandler() 0 4 1
A getSecurityHandler() 0 4 1
A isGranted() 0 11 4
A getUrlSafeIdentifier() 0 4 1
A getNormalizedIdentifier() 0 4 1
A id() 0 4 1
A setValidator() 0 11 2
A getValidator() 0 4 1
A getShow() 0 6 1
A setFormTheme() 0 4 1
A getFormTheme() 0 4 1
A setFilterTheme() 0 4 1
A getFilterTheme() 0 4 1
A addExtension() 0 4 1
A getExtensions() 0 4 1
A setMenuFactory() 0 4 1
A getMenuFactory() 0 4 1
A setRouteBuilder() 0 4 1
A getRouteBuilder() 0 4 1
A toString() 0 12 4
A setLabelTranslatorStrategy() 0 4 1
A getLabelTranslatorStrategy() 0 4 1
A supportsPreviewMode() 0 4 1
A setPerPageOptions() 0 9 1
A getPerPageOptions() 0 12 1
A setPagerType() 0 4 1
A getPagerType() 0 4 1
A determinedPerPageValue() 0 4 1
A isAclEnabled() 0 4 1
A getObjectMetadata() 0 4 1
A getListModes() 0 4 1
A setListMode() 0 8 2
A getListMode() 0 8 2
A getAccessMapping() 0 4 1
A checkAccess() 0 22 5
A hasAccess() 0 20 5
D configureActionButtons() 0 74 21
A getActionButtons() 0 13 3
A getDashboardActions() 0 27 5
A showMosaicButton() 0 8 2
A getSearchResultLink() 0 10 4
A isDefaultFilter() 0 11 3
A canAccessObject() 0 4 3
A configureQuery() 0 4 1
A getTemplateRegistry() 0 4 1
A getDefaultSortValues() 0 15 3
A getDefaultFilterValues() 0 15 3
A configureFormFields() 0 3 1
A configureListFields() 0 3 1
A configureDatagridFilters() 0 3 1
A configureShowFields() 0 3 1
A configureRoutes() 0 3 1
A configureBatchActions() 0 4 1
A configureSideMenu() 0 3 1
A configureTabMenu() 0 6 1
A buildShow() 0 15 3
B buildList() 0 56 8
B buildForm() 0 33 6
A getSubClass() 0 13 2
A attachInlineValidator() 0 27 4
A predefinePerPageOptions() 0 6 1
A getAccess() 0 25 3
A configureDefaultFilterValues() 0 3 1
A configureDefaultSortValues() 0 3 1
A buildRoutes() 0 23 3

How to fix   Complexity   

Complex Class

Complex classes like AbstractAdmin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractAdmin, and based on these observations, apply Extract Interface, too.

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 base code route refer to the prefix used to generate the route name.
251
     *
252
     * NEXT_MAJOR: remove this attribute.
253
     *
254
     * @deprecated This attribute is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0
255
     *
256
     * @var string
257
     */
258
    protected $baseCodeRoute = '';
259
260
    /**
261
     * NEXT_MAJOR: should be default array and private.
262
     *
263
     * @var string|array
264
     */
265
    protected $parentAssociationMapping;
266
267
    /**
268
     * Reference the parent FieldDescription related to this admin
269
     * only set for FieldDescription which is associated to an Sub Admin instance.
270
     *
271
     * @var FieldDescriptionInterface
272
     */
273
    protected $parentFieldDescription;
274
275
    /**
276
     * If true then the current admin is part of the nested admin set (from the url).
277
     *
278
     * @var bool
279
     */
280
    protected $currentChild = false;
281
282
    /**
283
     * The uniqid is used to avoid clashing with 2 admin related to the code
284
     * ie: a Block linked to a Block.
285
     *
286
     * @var string
287
     */
288
    protected $uniqid;
289
290
    /**
291
     * The Entity or Document manager.
292
     *
293
     * @var ModelManagerInterface
294
     */
295
    protected $modelManager;
296
297
    /**
298
     * The current request object.
299
     *
300
     * @var Request|null
301
     */
302
    protected $request;
303
304
    /**
305
     * The translator component.
306
     *
307
     * NEXT_MAJOR: remove this property
308
     *
309
     * @var \Symfony\Component\Translation\TranslatorInterface
310
     *
311
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
312
     */
313
    protected $translator;
314
315
    /**
316
     * The related form contractor.
317
     *
318
     * @var FormContractorInterface
319
     */
320
    protected $formContractor;
321
322
    /**
323
     * The related list builder.
324
     *
325
     * @var ListBuilderInterface
326
     */
327
    protected $listBuilder;
328
329
    /**
330
     * The related view builder.
331
     *
332
     * @var ShowBuilderInterface
333
     */
334
    protected $showBuilder;
335
336
    /**
337
     * The related datagrid builder.
338
     *
339
     * @var DatagridBuilderInterface
340
     */
341
    protected $datagridBuilder;
342
343
    /**
344
     * @var RouteBuilderInterface
345
     */
346
    protected $routeBuilder;
347
348
    /**
349
     * The datagrid instance.
350
     *
351
     * @var DatagridInterface|null
352
     */
353
    protected $datagrid;
354
355
    /**
356
     * The router instance.
357
     *
358
     * @var RouteGeneratorInterface|null
359
     */
360
    protected $routeGenerator;
361
362
    /**
363
     * The generated breadcrumbs.
364
     *
365
     * NEXT_MAJOR : remove this property
366
     *
367
     * @var array
368
     */
369
    protected $breadcrumbs = [];
370
371
    /**
372
     * @var SecurityHandlerInterface
373
     */
374
    protected $securityHandler;
375
376
    /**
377
     * @var ValidatorInterface
378
     */
379
    protected $validator;
380
381
    /**
382
     * The configuration pool.
383
     *
384
     * @var Pool
385
     */
386
    protected $configurationPool;
387
388
    /**
389
     * @var ItemInterface
390
     */
391
    protected $menu;
392
393
    /**
394
     * @var FactoryInterface
395
     */
396
    protected $menuFactory;
397
398
    /**
399
     * @var array<string, bool>
400
     */
401
    protected $loaded = [
402
        'view_fields' => false,
403
        'view_groups' => false,
404
        'routes' => false,
405
        'tab_menu' => false,
406
    ];
407
408
    /**
409
     * @var string[]
410
     */
411
    protected $formTheme = [];
412
413
    /**
414
     * @var string[]
415
     */
416
    protected $filterTheme = [];
417
418
    /**
419
     * @var array<string, string>
420
     *
421
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
422
     */
423
    protected $templates = [];
424
425
    /**
426
     * @var AdminExtensionInterface[]
427
     */
428
    protected $extensions = [];
429
430
    /**
431
     * @var LabelTranslatorStrategyInterface
432
     */
433
    protected $labelTranslatorStrategy;
434
435
    /**
436
     * Setting to true will enable preview mode for
437
     * the entity and show a preview button in the
438
     * edit/create forms.
439
     *
440
     * @var bool
441
     */
442
    protected $supportsPreviewMode = false;
443
444
    /**
445
     * Roles and permissions per role.
446
     *
447
     * @var array 'role' => ['permission', 'permission']
448
     */
449
    protected $securityInformation = [];
450
451
    protected $cacheIsGranted = [];
452
453
    /**
454
     * Action list for the search result.
455
     *
456
     * @var string[]
457
     */
458
    protected $searchResultActions = ['edit', 'show'];
459
460
    protected $listModes = [
461
        'list' => [
462
            'class' => 'fa fa-list fa-fw',
463
        ],
464
        'mosaic' => [
465
            'class' => self::MOSAIC_ICON_CLASS,
466
        ],
467
    ];
468
469
    /**
470
     * The Access mapping.
471
     *
472
     * @var array [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
473
     */
474
    protected $accessMapping = [];
475
476
    /**
477
     * @var MutableTemplateRegistryInterface
478
     */
479
    private $templateRegistry;
480
481
    /**
482
     * The class name managed by the admin class.
483
     *
484
     * @var string
485
     */
486
    private $class;
487
488
    /**
489
     * The subclasses supported by the admin class.
490
     *
491
     * @var array<string, string>
492
     */
493
    private $subClasses = [];
494
495
    /**
496
     * The list collection.
497
     *
498
     * @var FieldDescriptionCollection
499
     */
500
    private $list;
501
502
    /**
503
     * @var FieldDescriptionCollection|null
504
     */
505
    private $show;
506
507
    /**
508
     * @var Form|null
509
     */
510
    private $form;
511
512
    /**
513
     * The cached base route name.
514
     *
515
     * @var string
516
     */
517
    private $cachedBaseRouteName;
518
519
    /**
520
     * The cached base route pattern.
521
     *
522
     * @var string
523
     */
524
    private $cachedBaseRoutePattern;
525
526
    /**
527
     * The form group disposition.
528
     *
529
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
530
     * hold boolean values.
531
     *
532
     * @var array|bool
533
     */
534
    private $formGroups = false;
535
536
    /**
537
     * The form tabs disposition.
538
     *
539
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
540
     * hold boolean values.
541
     *
542
     * @var array|bool
543
     */
544
    private $formTabs = false;
545
546
    /**
547
     * The view group disposition.
548
     *
549
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
550
     * hold boolean values.
551
     *
552
     * @var array|bool
553
     */
554
    private $showGroups = false;
555
556
    /**
557
     * The view tab disposition.
558
     *
559
     * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
560
     * hold boolean values.
561
     *
562
     * @var array|bool
563
     */
564
    private $showTabs = false;
565
566
    /**
567
     * The manager type to use for the admin.
568
     *
569
     * @var string
570
     */
571
    private $managerType;
572
573
    /**
574
     * The breadcrumbsBuilder component.
575
     *
576
     * @var BreadcrumbsBuilderInterface
577
     */
578
    private $breadcrumbsBuilder;
579
580
    /**
581
     * Component responsible for persisting filters.
582
     *
583
     * @var FilterPersisterInterface|null
584
     */
585
    private $filterPersister;
586
587
    /**
588
     * @param string      $code
589
     * @param string      $class
590
     * @param string|null $baseControllerName
591
     */
592
    public function __construct($code, $class, $baseControllerName = null)
593
    {
594
        if (!\is_string($code)) {
595
            @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...
596
                '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.',
597
                __METHOD__
598
            ), E_USER_DEPRECATED);
599
        }
600
        $this->code = $code;
601
        if (!\is_string($class)) {
602
            @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...
603
                '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.',
604
                __METHOD__
605
            ), E_USER_DEPRECATED);
606
        }
607
        $this->class = $class;
608
        if (null !== $baseControllerName && !\is_string($baseControllerName)) {
609
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
610
                'Passing other type than string 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.',
611
                __METHOD__
612
            ), E_USER_DEPRECATED);
613
        }
614
        $this->baseControllerName = $baseControllerName;
615
616
        // NEXT_MAJOR: Remove this line.
617
        $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...
618
619
        // NEXT_MAJOR: Remove this line.
620
        $this->datagridValues['_per_page'] = $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...
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...
621
    }
622
623
    /**
624
     * {@inheritdoc}
625
     */
626
    public function getExportFormats()
627
    {
628
        return [
629
            'json', 'xml', 'csv', 'xls',
630
        ];
631
    }
632
633
    /**
634
     * @return array
635
     */
636
    public function getExportFields()
637
    {
638
        $fields = $this->getModelManager()->getExportFields($this->getClass());
639
640
        foreach ($this->getExtensions() as $extension) {
641
            if (method_exists($extension, 'configureExportFields')) {
642
                $fields = $extension->configureExportFields($this, $fields);
643
            }
644
        }
645
646
        return $fields;
647
    }
648
649
    public function getDataSourceIterator()
650
    {
651
        $datagrid = $this->getDatagrid();
652
        $datagrid->buildPager();
653
654
        $fields = [];
655
656
        foreach ($this->getExportFields() as $key => $field) {
657
            $label = $this->getTranslationLabel($field, 'export', 'label');
658
            $transLabel = $this->trans($label);
659
660
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
661
            // No translation key exists
662
            if ($transLabel === $label) {
663
                $fields[$key] = $field;
664
            } else {
665
                $fields[$transLabel] = $field;
666
            }
667
        }
668
669
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
0 ignored issues
show
Bug introduced by
It seems like $datagrid defined by $this->getDatagrid() on line 651 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...
670
    }
671
672
    public function validate(ErrorElement $errorElement, $object)
673
    {
674
    }
675
676
    /**
677
     * define custom variable.
678
     */
679
    public function initialize()
680
    {
681
        if (!$this->classnameLabel) {
682
            /* NEXT_MAJOR: remove cast to string, null is not supposed to be
683
            supported but was documented as such */
684
            $this->classnameLabel = substr(
685
                (string) $this->getClass(),
686
                strrpos((string) $this->getClass(), '\\') + 1
687
            );
688
        }
689
690
        // NEXT_MAJOR: Remove this line.
691
        $this->baseCodeRoute = $this->getCode();
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0

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

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

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

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

Loading history...
764
    {
765
    }
766
767
    public function preUpdate($object)
768
    {
769
    }
770
771
    public function postUpdate($object)
772
    {
773
    }
774
775
    public function prePersist($object)
776
    {
777
    }
778
779
    public function postPersist($object)
780
    {
781
    }
782
783
    public function preRemove($object)
784
    {
785
    }
786
787
    public function postRemove($object)
788
    {
789
    }
790
791
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
792
    {
793
    }
794
795
    public function getFilterParameters()
796
    {
797
        $parameters = [];
798
799
        // build the values array
800
        if ($this->hasRequest()) {
801
            $filters = $this->request->query->get('filter', []);
802
            if (isset($filters['_page'])) {
803
                $filters['_page'] = (int) $filters['_page'];
804
            }
805
            if (isset($filters['_per_page'])) {
806
                $filters['_per_page'] = (int) $filters['_per_page'];
807
            }
808
809
            // if filter persistence is configured
810
            // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
811
            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...
812
                // if reset filters is asked, remove from storage
813
                if ('reset' === $this->request->query->get('filters')) {
814
                    $this->filterPersister->reset($this->getCode());
815
                }
816
817
                // if no filters, fetch from storage
818
                // otherwise save to storage
819
                if (empty($filters)) {
820
                    $filters = $this->filterPersister->get($this->getCode());
821
                } else {
822
                    $this->filterPersister->set($this->getCode(), $filters);
823
                }
824
            }
825
826
            $parameters = array_merge(
827
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
828
                $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...
829
                $this->getDefaultSortValues(),
830
                $this->getDefaultFilterValues(),
831
                $filters
832
            );
833
834
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
835
                $parameters['_per_page'] = $this->getMaxPerPage();
836
            }
837
838
            // always force the parent value
839
            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...
840
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
841
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
842
            }
843
        }
844
845
        return $parameters;
846
    }
847
848
    public function buildDatagrid()
849
    {
850
        if ($this->datagrid) {
851
            return;
852
        }
853
854
        $filterParameters = $this->getFilterParameters();
855
856
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
857
        if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
858
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
859
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
860
            } else {
861
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
862
                    $this->getClass(),
863
                    $filterParameters['_sort_by'],
864
                    []
865
                );
866
867
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
868
            }
869
        }
870
871
        // initialize the datagrid
872
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
873
874
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
875
876
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
877
878
        // build the datagrid filter
879
        $this->configureDatagridFilters($mapper);
880
881
        // ok, try to limit to add parent filter
882
        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...
883
            $mapper->add($this->getParentAssociationMapping(), null, [
884
                'show_filter' => false,
885
                'label' => false,
886
                'field_type' => ModelHiddenType::class,
887
                'field_options' => [
888
                    'model_manager' => $this->getModelManager(),
889
                ],
890
                'operator_type' => HiddenType::class,
891
            ], null, null, [
892
                'admin_code' => $this->getParent()->getCode(),
893
            ]);
894
        }
895
896
        foreach ($this->getExtensions() as $extension) {
897
            $extension->configureDatagridFilters($mapper);
898
        }
899
    }
900
901
    /**
902
     * Returns the name of the parent related field, so the field can be use to set the default
903
     * value (ie the parent object) or to filter the object.
904
     *
905
     * @throws \InvalidArgumentException
906
     *
907
     * @return string|null
908
     */
909
    public function getParentAssociationMapping()
910
    {
911
        // NEXT_MAJOR: remove array check
912
        if (\is_array($this->parentAssociationMapping) && $this->isChild()) {
913
            $parent = $this->getParent()->getCode();
914
915
            if (\array_key_exists($parent, $this->parentAssociationMapping)) {
916
                return $this->parentAssociationMapping[$parent];
917
            }
918
919
            throw new \InvalidArgumentException(sprintf(
920
                "There's no association between %s and %s.",
921
                $this->getCode(),
922
                $this->getParent()->getCode()
923
            ));
924
        }
925
926
        // NEXT_MAJOR: remove this line
927
        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 927 which is incompatible with the return type documented by Sonata\AdminBundle\Admin...arentAssociationMapping of type string|null.
Loading history...
928
    }
929
930
    /**
931
     * @param string $code
932
     * @param string $value
933
     */
934
    final public function addParentAssociationMapping($code, $value)
935
    {
936
        $this->parentAssociationMapping[$code] = $value;
937
    }
938
939
    /**
940
     * Returns the baseRoutePattern used to generate the routing information.
941
     *
942
     * @throws \RuntimeException
943
     *
944
     * @return string the baseRoutePattern used to generate the routing information
945
     */
946
    public function getBaseRoutePattern()
947
    {
948
        if (null !== $this->cachedBaseRoutePattern) {
949
            return $this->cachedBaseRoutePattern;
950
        }
951
952
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
953
            $baseRoutePattern = $this->baseRoutePattern;
954
            if (!$this->baseRoutePattern) {
955
                preg_match(self::CLASS_REGEX, $this->class, $matches);
956
957
                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...
958
                    throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', static::class));
959
                }
960
                $baseRoutePattern = $this->urlize($matches[5], '-');
961
            }
962
963
            $this->cachedBaseRoutePattern = sprintf(
964
                '%s/%s/%s',
965
                $this->getParent()->getBaseRoutePattern(),
966
                $this->getParent()->getRouterIdParameter(),
967
                $baseRoutePattern
968
            );
969
        } elseif ($this->baseRoutePattern) {
970
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
971
        } else {
972
            preg_match(self::CLASS_REGEX, $this->class, $matches);
973
974
            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...
975
                throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', static::class));
976
            }
977
978
            $this->cachedBaseRoutePattern = sprintf(
979
                '/%s%s/%s',
980
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
981
                $this->urlize($matches[3], '-'),
982
                $this->urlize($matches[5], '-')
983
            );
984
        }
985
986
        return $this->cachedBaseRoutePattern;
987
    }
988
989
    /**
990
     * Returns the baseRouteName used to generate the routing information.
991
     *
992
     * @throws \RuntimeException
993
     *
994
     * @return string the baseRouteName used to generate the routing information
995
     */
996
    public function getBaseRouteName()
997
    {
998
        if (null !== $this->cachedBaseRouteName) {
999
            return $this->cachedBaseRouteName;
1000
        }
1001
1002
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
1003
            $baseRouteName = $this->baseRouteName;
1004
            if (!$this->baseRouteName) {
1005
                preg_match(self::CLASS_REGEX, $this->class, $matches);
1006
1007
                if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1008
                    throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', static::class));
1009
                }
1010
                $baseRouteName = $this->urlize($matches[5]);
1011
            }
1012
1013
            $this->cachedBaseRouteName = sprintf(
1014
                '%s_%s',
1015
                $this->getParent()->getBaseRouteName(),
1016
                $baseRouteName
1017
            );
1018
        } elseif ($this->baseRouteName) {
1019
            $this->cachedBaseRouteName = $this->baseRouteName;
1020
        } else {
1021
            preg_match(self::CLASS_REGEX, $this->class, $matches);
1022
1023
            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...
1024
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', static::class));
1025
            }
1026
1027
            $this->cachedBaseRouteName = sprintf(
1028
                'admin_%s%s_%s',
1029
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
1030
                $this->urlize($matches[3]),
1031
                $this->urlize($matches[5])
1032
            );
1033
        }
1034
1035
        return $this->cachedBaseRouteName;
1036
    }
1037
1038
    /**
1039
     * urlize the given word.
1040
     *
1041
     * @param string $word
1042
     * @param string $sep  the separator
1043
     *
1044
     * @return string
1045
     */
1046
    public function urlize($word, $sep = '_')
1047
    {
1048
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
1049
    }
1050
1051
    public function getClass()
1052
    {
1053
        if ($this->hasActiveSubClass()) {
1054
            if ($this->hasParentFieldDescription()) {
1055
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
1056
            }
1057
1058
            $subClass = $this->getRequest()->query->get('subclass');
1059
1060
            if (!$this->hasSubClass($subClass)) {
1061
                throw new \RuntimeException(sprintf('Subclass "%s" is not defined.', $subClass));
1062
            }
1063
1064
            return $this->getSubClass($subClass);
1065
        }
1066
1067
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
1068
        if ($this->subject && \is_object($this->subject)) {
1069
            return ClassUtils::getClass($this->subject);
1070
        }
1071
1072
        return $this->class;
1073
    }
1074
1075
    public function getSubClasses()
1076
    {
1077
        return $this->subClasses;
1078
    }
1079
1080
    /**
1081
     * NEXT_MAJOR: remove this method.
1082
     */
1083
    public function addSubClass($subClass)
1084
    {
1085
        @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...
1086
            'Method "%s" is deprecated since sonata-project/admin-bundle 3.30 and will be removed in 4.0.',
1087
            __METHOD__
1088
        ), E_USER_DEPRECATED);
1089
1090
        if (!\in_array($subClass, $this->subClasses, true)) {
1091
            $this->subClasses[] = $subClass;
1092
        }
1093
    }
1094
1095
    public function setSubClasses(array $subClasses)
1096
    {
1097
        $this->subClasses = $subClasses;
1098
    }
1099
1100
    public function hasSubClass($name)
1101
    {
1102
        return isset($this->subClasses[$name]);
1103
    }
1104
1105
    public function hasActiveSubClass()
1106
    {
1107
        if (\count($this->subClasses) > 0 && $this->request) {
1108
            return null !== $this->getRequest()->query->get('subclass');
1109
        }
1110
1111
        return false;
1112
    }
1113
1114
    public function getActiveSubClass()
1115
    {
1116
        if (!$this->hasActiveSubClass()) {
1117
            @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...
1118
                '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. '.
1119
                'Use %s::hasActiveSubClass() to know if there is an active subclass.',
1120
                __METHOD__,
1121
                __CLASS__
1122
            ), E_USER_DEPRECATED);
1123
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1124
            // throw new \LogicException(sprintf(
1125
            //    'Admin "%s" has no active subclass.',
1126
            //    static::class
1127
            // ));
1128
1129
            return null;
1130
        }
1131
1132
        return $this->getSubClass($this->getActiveSubclassCode());
1133
    }
1134
1135
    public function getActiveSubclassCode()
1136
    {
1137
        if (!$this->hasActiveSubClass()) {
1138
            @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...
1139
                '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. '.
1140
                'Use %s::hasActiveSubClass() to know if there is an active subclass.',
1141
                __METHOD__,
1142
                __CLASS__
1143
            ), E_USER_DEPRECATED);
1144
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1145
            // throw new \LogicException(sprintf(
1146
            //    'Admin "%s" has no active subclass.',
1147
            //    static::class
1148
            // ));
1149
1150
            return null;
1151
        }
1152
1153
        $subClass = $this->getRequest()->query->get('subclass');
1154
1155
        if (!$this->hasSubClass($subClass)) {
1156
            @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...
1157
                '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. '.
1158
                'Use %s::hasActiveSubClass() to know if there is an active subclass.',
1159
                __METHOD__,
1160
                __CLASS__
1161
            ), E_USER_DEPRECATED);
1162
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
1163
            // throw new \LogicException(sprintf(
1164
            //    'Admin "%s" has no active subclass.',
1165
            //    static::class
1166
            // ));
1167
1168
            return null;
1169
        }
1170
1171
        return $subClass;
1172
    }
1173
1174
    public function getBatchActions()
1175
    {
1176
        $actions = [];
1177
1178
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1179
            $actions['delete'] = [
1180
                'label' => 'action_delete',
1181
                'translation_domain' => 'SonataAdminBundle',
1182
                'ask_confirmation' => true, // by default always true
1183
            ];
1184
        }
1185
1186
        $actions = $this->configureBatchActions($actions);
1187
1188
        foreach ($this->getExtensions() as $extension) {
1189
            // NEXT_MAJOR: remove method check
1190
            if (method_exists($extension, 'configureBatchActions')) {
1191
                $actions = $extension->configureBatchActions($this, $actions);
1192
            }
1193
        }
1194
1195
        foreach ($actions  as $name => &$action) {
1196
            if (!\array_key_exists('label', $action)) {
1197
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1198
            }
1199
1200
            if (!\array_key_exists('translation_domain', $action)) {
1201
                $action['translation_domain'] = $this->getTranslationDomain();
1202
            }
1203
        }
1204
1205
        return $actions;
1206
    }
1207
1208
    public function getRoutes()
1209
    {
1210
        $this->buildRoutes();
1211
1212
        return $this->routes;
1213
    }
1214
1215
    public function getRouterIdParameter()
1216
    {
1217
        return '{'.$this->getIdParameter().'}';
1218
    }
1219
1220
    public function getIdParameter()
1221
    {
1222
        $parameter = 'id';
1223
1224
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1225
            $parameter = 'child'.ucfirst($parameter);
1226
        }
1227
1228
        return $parameter;
1229
    }
1230
1231
    public function hasRoute($name)
1232
    {
1233
        if (!$this->routeGenerator) {
1234
            throw new \RuntimeException('RouteGenerator cannot be null');
1235
        }
1236
1237
        return $this->routeGenerator->hasAdminRoute($this, $name);
1238
    }
1239
1240
    /**
1241
     * @param string      $name
1242
     * @param string|null $adminCode
1243
     *
1244
     * @return bool
1245
     */
1246
    public function isCurrentRoute($name, $adminCode = null)
1247
    {
1248
        if (!$this->hasRequest()) {
1249
            return false;
1250
        }
1251
1252
        $request = $this->getRequest();
1253
        $route = $request->get('_route');
1254
1255
        if ($adminCode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $adminCode of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

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

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

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

Loading history...
1297
1298
        $this->getTemplateRegistry()->setTemplates($templates);
1299
    }
1300
1301
    /**
1302
     * @param string $name
1303
     * @param string $template
1304
     */
1305
    public function setTemplate($name, $template)
1306
    {
1307
        // NEXT_MAJOR: Remove this line
1308
        $this->templates[$name] = $template;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin\AbstractAdmin::$templates has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
1309
1310
        $this->getTemplateRegistry()->setTemplate($name, $template);
1311
    }
1312
1313
    /**
1314
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1315
     *
1316
     * @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...
1317
     */
1318
    public function getTemplates()
1319
    {
1320
        return $this->getTemplateRegistry()->getTemplates();
1321
    }
1322
1323
    /**
1324
     * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1325
     *
1326
     * @param string $name
1327
     *
1328
     * @return string|null
1329
     */
1330
    public function getTemplate($name)
1331
    {
1332
        return $this->getTemplateRegistry()->getTemplate($name);
1333
    }
1334
1335
    public function getNewInstance()
1336
    {
1337
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1338
        foreach ($this->getExtensions() as $extension) {
1339
            $extension->alterNewInstance($this, $object);
1340
        }
1341
1342
        return $object;
1343
    }
1344
1345
    public function getFormBuilder()
1346
    {
1347
        $this->formOptions['data_class'] = $this->getClass();
1348
1349
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1350
            $this->getUniqid(),
1351
            $this->formOptions
1352
        );
1353
1354
        $this->defineFormBuilder($formBuilder);
1355
1356
        return $formBuilder;
1357
    }
1358
1359
    /**
1360
     * This method is being called by the main admin class and the child class,
1361
     * the getFormBuilder is only call by the main admin class.
1362
     */
1363
    public function defineFormBuilder(FormBuilderInterface $formBuilder)
1364
    {
1365
        if (!$this->hasSubject()) {
1366
            @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...
1367
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.65 and will throw an exception in 4.0. '.
1368
                'Use %s::setSubject() to set the subject.',
1369
                __METHOD__,
1370
                __CLASS__
1371
            ), E_USER_DEPRECATED);
1372
            // NEXT_MAJOR : remove the previous `trigger_error()` call and uncomment the following exception
1373
            // throw new \LogicException(sprintf(
1374
            //    'Admin "%s" has no subject.',
1375
            //    static::class
1376
            // ));
1377
        }
1378
1379
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1380
1381
        $this->configureFormFields($mapper);
1382
1383
        foreach ($this->getExtensions() as $extension) {
1384
            $extension->configureFormFields($mapper);
1385
        }
1386
1387
        $this->attachInlineValidator();
1388
    }
1389
1390
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
1391
    {
1392
        $pool = $this->getConfigurationPool();
1393
1394
        $adminCode = $fieldDescription->getOption('admin_code');
1395
1396
        if (null !== $adminCode) {
1397
            $admin = $pool->getAdminByAdminCode($adminCode);
1398
        } else {
1399
            $admin = $pool->getAdminByClass($fieldDescription->getTargetEntity());
1400
        }
1401
1402
        if (!$admin) {
1403
            return;
1404
        }
1405
1406
        if ($this->hasRequest()) {
1407
            $admin->setRequest($this->getRequest());
1408
        }
1409
1410
        $fieldDescription->setAssociationAdmin($admin);
1411
    }
1412
1413
    public function getObject($id)
1414
    {
1415
        $object = $this->getModelManager()->find($this->getClass(), $id);
1416
        foreach ($this->getExtensions() as $extension) {
1417
            $extension->alterObject($this, $object);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getModelManager()...$this->getClass(), $id) on line 1415 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...
1418
        }
1419
1420
        return $object;
1421
    }
1422
1423
    public function getForm()
1424
    {
1425
        $this->buildForm();
1426
1427
        return $this->form;
1428
    }
1429
1430
    public function getList()
1431
    {
1432
        $this->buildList();
1433
1434
        return $this->list;
1435
    }
1436
1437
    /**
1438
     * @final since sonata-project/admin-bundle 3.63.0
1439
     */
1440
    public function createQuery($context = 'list')
1441
    {
1442
        if (\func_num_args() > 0) {
1443
            @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...
1444
                'The $context argument of '.__METHOD__.' is deprecated since 3.3, to be removed in 4.0.',
1445
                E_USER_DEPRECATED
1446
            );
1447
        }
1448
1449
        $query = $this->getModelManager()->createQuery($this->getClass());
1450
1451
        $query = $this->configureQuery($query);
1452
        foreach ($this->extensions as $extension) {
1453
            $extension->configureQuery($this, $query, $context);
1454
        }
1455
1456
        return $query;
1457
    }
1458
1459
    public function getDatagrid()
1460
    {
1461
        $this->buildDatagrid();
1462
1463
        return $this->datagrid;
1464
    }
1465
1466
    public function buildTabMenu($action, ?AdminInterface $childAdmin = null)
1467
    {
1468
        if ($this->loaded['tab_menu']) {
1469
            return $this->menu;
1470
        }
1471
1472
        $this->loaded['tab_menu'] = true;
1473
1474
        $menu = $this->menuFactory->createItem('root');
1475
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1476
        $menu->setExtra('translation_domain', $this->translationDomain);
1477
1478
        // Prevents BC break with KnpMenuBundle v1.x
1479
        if (method_exists($menu, 'setCurrentUri')) {
1480
            $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...
1481
        }
1482
1483
        $this->configureTabMenu($menu, $action, $childAdmin);
1484
1485
        foreach ($this->getExtensions() as $extension) {
1486
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1487
        }
1488
1489
        $this->menu = $menu;
1490
1491
        return $this->menu;
1492
    }
1493
1494
    public function buildSideMenu($action, ?AdminInterface $childAdmin = null)
1495
    {
1496
        return $this->buildTabMenu($action, $childAdmin);
1497
    }
1498
1499
    /**
1500
     * @param string $action
1501
     *
1502
     * @return ItemInterface
1503
     */
1504
    public function getSideMenu($action, ?AdminInterface $childAdmin = null)
1505
    {
1506
        if ($this->isChild()) {
1507
            return $this->getParent()->getSideMenu($action, $this);
1508
        }
1509
1510
        $this->buildSideMenu($action, $childAdmin);
1511
1512
        return $this->menu;
1513
    }
1514
1515
    /**
1516
     * Returns the root code.
1517
     *
1518
     * @return string the root code
1519
     */
1520
    public function getRootCode()
1521
    {
1522
        return $this->getRoot()->getCode();
1523
    }
1524
1525
    /**
1526
     * Returns the master admin.
1527
     *
1528
     * @return AbstractAdmin the root admin class
1529
     */
1530
    public function getRoot()
1531
    {
1532
        if (!$this->hasParentFieldDescription()) {
1533
            return $this;
1534
        }
1535
1536
        return $this->getParentFieldDescription()->getAdmin()->getRoot();
1537
    }
1538
1539
    public function setBaseControllerName($baseControllerName)
1540
    {
1541
        $this->baseControllerName = $baseControllerName;
1542
    }
1543
1544
    public function getBaseControllerName()
1545
    {
1546
        return $this->baseControllerName;
1547
    }
1548
1549
    /**
1550
     * @param string $label
1551
     */
1552
    public function setLabel($label)
1553
    {
1554
        $this->label = $label;
1555
    }
1556
1557
    public function getLabel()
1558
    {
1559
        return $this->label;
1560
    }
1561
1562
    /**
1563
     * @param bool $persist
1564
     *
1565
     * NEXT_MAJOR: remove this method
1566
     *
1567
     * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
1568
     */
1569
    public function setPersistFilters($persist)
1570
    {
1571
        @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...
1572
            'The '.__METHOD__.' method is deprecated since version 3.34 and will be removed in 4.0.',
1573
            E_USER_DEPRECATED
1574
        );
1575
1576
        $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...
1577
    }
1578
1579
    public function setFilterPersister(?FilterPersisterInterface $filterPersister = null)
1580
    {
1581
        $this->filterPersister = $filterPersister;
1582
        // NEXT_MAJOR remove the deprecated property will be removed. Needed for persisted filter condition.
1583
        $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...
1584
    }
1585
1586
    /**
1587
     * NEXT_MAJOR: Remove this method.
1588
     *
1589
     * @deprecated since sonata-project/admin-bundle 3.x, to be removed in 4.0.
1590
     *
1591
     * @param int $maxPerPage
1592
     */
1593
    public function setMaxPerPage($maxPerPage)
1594
    {
1595
        @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...
1596
            'The method %s is deprecated since sonata-project/admin-bundle 3.x and will be removed in 4.0.',
1597
            __METHOD__
1598
        ), E_USER_DEPRECATED);
1599
1600
        $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...
1601
    }
1602
1603
    /**
1604
     * @return int
1605
     */
1606
    public function getMaxPerPage()
1607
    {
1608
        // NEXT_MAJOR: Remove this line and uncomment the following.
1609
        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...
1610
        // $sortValues = $this->getModelManager()->getDefaultSortValues($this->class);
1611
1612
        // return $sortValues['_per_page'] ?? 25;
1613
    }
1614
1615
    /**
1616
     * @param int $maxPageLinks
1617
     */
1618
    public function setMaxPageLinks($maxPageLinks)
1619
    {
1620
        $this->maxPageLinks = $maxPageLinks;
1621
    }
1622
1623
    /**
1624
     * @return int
1625
     */
1626
    public function getMaxPageLinks()
1627
    {
1628
        return $this->maxPageLinks;
1629
    }
1630
1631
    public function getFormGroups()
1632
    {
1633
        if (!\is_array($this->formGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1634
            @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...
1635
                '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.',
1636
                __METHOD__
1637
            ), E_USER_DEPRECATED);
1638
        }
1639
1640
        return $this->formGroups;
1641
    }
1642
1643
    public function setFormGroups(array $formGroups)
1644
    {
1645
        $this->formGroups = $formGroups;
1646
    }
1647
1648
    public function removeFieldFromFormGroup($key)
1649
    {
1650
        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...
1651
            unset($this->formGroups[$name]['fields'][$key]);
1652
1653
            if (empty($this->formGroups[$name]['fields'])) {
1654
                unset($this->formGroups[$name]);
1655
            }
1656
        }
1657
    }
1658
1659
    /**
1660
     * @param string $group
1661
     */
1662
    public function reorderFormGroup($group, array $keys)
1663
    {
1664
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1665
        $formGroups = $this->getFormGroups('sonata_deprecation_mute');
1666
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1667
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups('sonata_deprecation_mute') on line 1665 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...
1668
    }
1669
1670
    public function getFormTabs()
1671
    {
1672
        if (!\is_array($this->formTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1673
            @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...
1674
                '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.',
1675
                __METHOD__
1676
            ), E_USER_DEPRECATED);
1677
        }
1678
1679
        return $this->formTabs;
1680
    }
1681
1682
    public function setFormTabs(array $formTabs)
1683
    {
1684
        $this->formTabs = $formTabs;
1685
    }
1686
1687
    public function getShowTabs()
1688
    {
1689
        if (!\is_array($this->showTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1690
            @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...
1691
                '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.',
1692
                __METHOD__
1693
            ), E_USER_DEPRECATED);
1694
        }
1695
1696
        return $this->showTabs;
1697
    }
1698
1699
    public function setShowTabs(array $showTabs)
1700
    {
1701
        $this->showTabs = $showTabs;
1702
    }
1703
1704
    public function getShowGroups()
1705
    {
1706
        if (!\is_array($this->showGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
1707
            @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...
1708
                '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.',
1709
                __METHOD__
1710
            ), E_USER_DEPRECATED);
1711
        }
1712
1713
        return $this->showGroups;
1714
    }
1715
1716
    public function setShowGroups(array $showGroups)
1717
    {
1718
        $this->showGroups = $showGroups;
1719
    }
1720
1721
    public function reorderShowGroup($group, array $keys)
1722
    {
1723
        // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
1724
        $showGroups = $this->getShowGroups('sonata_deprecation_mute');
1725
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1726
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups('sonata_deprecation_mute') on line 1724 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...
1727
    }
1728
1729
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
1730
    {
1731
        $this->parentFieldDescription = $parentFieldDescription;
1732
    }
1733
1734
    public function getParentFieldDescription()
1735
    {
1736
        if (!$this->hasParentFieldDescription()) {
1737
            @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...
1738
                '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. '.
1739
                'Use %s::hasParentFieldDescription() to know if there is a parent field description.',
1740
                __METHOD__,
1741
                __CLASS__
1742
            ), E_USER_DEPRECATED);
1743
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1744
            // throw new \LogicException(sprintf(
1745
            //    'Admin "%s" has no parent field description.',
1746
            //    static::class
1747
            // ));
1748
1749
            return null;
1750
        }
1751
1752
        return $this->parentFieldDescription;
1753
    }
1754
1755
    public function hasParentFieldDescription()
1756
    {
1757
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1758
    }
1759
1760
    public function setSubject($subject)
1761
    {
1762
        if (\is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1763
            $message = <<<'EOT'
1764
You are trying to set entity an instance of "%s",
1765
which is not the one registered with this admin class ("%s").
1766
This is deprecated since 3.5 and will no longer be supported in 4.0.
1767
EOT;
1768
1769
            @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...
1770
                sprintf($message, \get_class($subject), $this->getClass()),
1771
                E_USER_DEPRECATED
1772
            ); // NEXT_MAJOR : throw an exception instead
1773
        }
1774
1775
        $this->subject = $subject;
1776
    }
1777
1778
    public function getSubject()
1779
    {
1780
        if (!$this->hasSubject()) {
1781
            @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...
1782
                'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
1783
                'Use %s::hasSubject() to know if there is a subject.',
1784
                __METHOD__,
1785
                __CLASS__
1786
            ), E_USER_DEPRECATED);
1787
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and update the return type
1788
            // throw new \LogicException(sprintf(
1789
            //    'Admin "%s" has no subject.',
1790
            //    static::class
1791
            // ));
1792
1793
            return null;
1794
        }
1795
1796
        return $this->subject;
1797
    }
1798
1799
    public function hasSubject()
1800
    {
1801
        if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
1802
            $id = $this->request->get($this->getIdParameter());
1803
1804
            if (null !== $id) {
1805
                $this->subject = $this->getObject($id);
1806
            }
1807
        }
1808
1809
        return null !== $this->subject;
1810
    }
1811
1812
    public function getFormFieldDescriptions()
1813
    {
1814
        $this->buildForm();
1815
1816
        return $this->formFieldDescriptions;
1817
    }
1818
1819
    public function getFormFieldDescription($name)
1820
    {
1821
        return $this->hasFormFieldDescription($name) ? $this->formFieldDescriptions[$name] : null;
1822
    }
1823
1824
    /**
1825
     * Returns true if the admin has a FieldDescription with the given $name.
1826
     *
1827
     * @param string $name
1828
     *
1829
     * @return bool
1830
     */
1831
    public function hasFormFieldDescription($name)
1832
    {
1833
        return \array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1834
    }
1835
1836
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1837
    {
1838
        $this->formFieldDescriptions[$name] = $fieldDescription;
1839
    }
1840
1841
    /**
1842
     * remove a FieldDescription.
1843
     *
1844
     * @param string $name
1845
     */
1846
    public function removeFormFieldDescription($name)
1847
    {
1848
        unset($this->formFieldDescriptions[$name]);
1849
    }
1850
1851
    /**
1852
     * build and return the collection of form FieldDescription.
1853
     *
1854
     * @return array collection of form FieldDescription
1855
     */
1856
    public function getShowFieldDescriptions()
1857
    {
1858
        $this->buildShow();
1859
1860
        return $this->showFieldDescriptions;
1861
    }
1862
1863
    /**
1864
     * Returns the form FieldDescription with the given $name.
1865
     *
1866
     * @param string $name
1867
     *
1868
     * @return FieldDescriptionInterface
1869
     */
1870
    public function getShowFieldDescription($name)
1871
    {
1872
        $this->buildShow();
1873
1874
        return $this->hasShowFieldDescription($name) ? $this->showFieldDescriptions[$name] : null;
1875
    }
1876
1877
    public function hasShowFieldDescription($name)
1878
    {
1879
        return \array_key_exists($name, $this->showFieldDescriptions);
1880
    }
1881
1882
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1883
    {
1884
        $this->showFieldDescriptions[$name] = $fieldDescription;
1885
    }
1886
1887
    public function removeShowFieldDescription($name)
1888
    {
1889
        unset($this->showFieldDescriptions[$name]);
1890
    }
1891
1892
    public function getListFieldDescriptions()
1893
    {
1894
        $this->buildList();
1895
1896
        return $this->listFieldDescriptions;
1897
    }
1898
1899
    public function getListFieldDescription($name)
1900
    {
1901
        if (!$this->hasListFieldDescription($name)) {
1902
            @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...
1903
                '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. '.
1904
                'Use %s::hasListFieldDescription(\'%s\') to know if there is a list field description.',
1905
                __METHOD__,
1906
                __CLASS__,
1907
                $name
1908
            ), E_USER_DEPRECATED);
1909
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
1910
            // throw new \LogicException(sprintf(
1911
            //    'Admin "%s" has no list field description for %s.',
1912
            //    static::class,
1913
            //    $name
1914
            // ));
1915
1916
            return null;
1917
        }
1918
1919
        return $this->listFieldDescriptions[$name];
1920
    }
1921
1922
    public function hasListFieldDescription($name)
1923
    {
1924
        $this->buildList();
1925
1926
        return \array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1927
    }
1928
1929
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1930
    {
1931
        $this->listFieldDescriptions[$name] = $fieldDescription;
1932
    }
1933
1934
    public function removeListFieldDescription($name)
1935
    {
1936
        unset($this->listFieldDescriptions[$name]);
1937
    }
1938
1939
    public function getFilterFieldDescription($name)
1940
    {
1941
        return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null;
1942
    }
1943
1944
    public function hasFilterFieldDescription($name)
1945
    {
1946
        return \array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1947
    }
1948
1949
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1950
    {
1951
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1952
    }
1953
1954
    public function removeFilterFieldDescription($name)
1955
    {
1956
        unset($this->filterFieldDescriptions[$name]);
1957
    }
1958
1959
    public function getFilterFieldDescriptions()
1960
    {
1961
        $this->buildDatagrid();
1962
1963
        return $this->filterFieldDescriptions;
1964
    }
1965
1966
    public function addChild(AdminInterface $child)
1967
    {
1968
        $parentAdmin = $this;
1969
        while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
1970
            $parentAdmin = $parentAdmin->getParent();
1971
        }
1972
1973
        if ($parentAdmin->getCode() === $child->getCode()) {
1974
            throw new \RuntimeException(sprintf(
1975
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1976
                $child->getCode(),
1977
                $this->getCode()
1978
            ));
1979
        }
1980
1981
        $this->children[$child->getCode()] = $child;
1982
1983
        $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...
1984
1985
        // NEXT_MAJOR: remove $args and add $field parameter to this function on next Major
1986
1987
        $args = \func_get_args();
1988
1989
        if (isset($args[1])) {
1990
            $child->addParentAssociationMapping($this->getCode(), $args[1]);
1991
        } else {
1992
            @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...
1993
                'Calling "addChild" without second argument is deprecated since'
1994
                .' sonata-project/admin-bundle 3.35 and will not be allowed in 4.0.',
1995
                E_USER_DEPRECATED
1996
            );
1997
        }
1998
    }
1999
2000
    public function hasChild($code)
2001
    {
2002
        return isset($this->children[$code]);
2003
    }
2004
2005
    public function getChildren()
2006
    {
2007
        return $this->children;
2008
    }
2009
2010
    public function getChild($code)
2011
    {
2012
        return $this->hasChild($code) ? $this->children[$code] : null;
2013
    }
2014
2015
    public function setParent(AdminInterface $parent)
2016
    {
2017
        $this->parent = $parent;
2018
    }
2019
2020
    public function getParent()
2021
    {
2022
        if (!$this->isChild()) {
2023
            @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...
2024
                'Calling %s() when there is no parent is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. '.
2025
                'Use %s::isChild() to know if there is a parent.',
2026
                __METHOD__,
2027
                __CLASS__
2028
            ), E_USER_DEPRECATED);
2029
            // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
2030
            // throw new \LogicException(sprintf(
2031
            //    'Admin "%s" has no parent.',
2032
            //    static::class
2033
            // ));
2034
2035
            return null;
2036
        }
2037
2038
        return $this->parent;
2039
    }
2040
2041
    final public function getRootAncestor()
2042
    {
2043
        $parent = $this;
2044
2045
        while ($parent->isChild()) {
2046
            $parent = $parent->getParent();
2047
        }
2048
2049
        return $parent;
2050
    }
2051
2052
    final public function getChildDepth()
2053
    {
2054
        $parent = $this;
2055
        $depth = 0;
2056
2057
        while ($parent->isChild()) {
2058
            $parent = $parent->getParent();
2059
            ++$depth;
2060
        }
2061
2062
        return $depth;
2063
    }
2064
2065
    final public function getCurrentLeafChildAdmin()
2066
    {
2067
        $child = $this->getCurrentChildAdmin();
2068
2069
        if (null === $child) {
2070
            return null;
2071
        }
2072
2073
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
2074
            $child = $c;
2075
        }
2076
2077
        return $child;
2078
    }
2079
2080
    public function isChild()
2081
    {
2082
        return $this->parent instanceof AdminInterface;
2083
    }
2084
2085
    /**
2086
     * Returns true if the admin has children, false otherwise.
2087
     *
2088
     * @return bool if the admin has children
2089
     */
2090
    public function hasChildren()
2091
    {
2092
        return \count($this->children) > 0;
2093
    }
2094
2095
    public function setUniqid($uniqid)
2096
    {
2097
        $this->uniqid = $uniqid;
2098
    }
2099
2100
    public function getUniqid()
2101
    {
2102
        if (!$this->uniqid) {
2103
            $this->uniqid = 's'.uniqid();
2104
        }
2105
2106
        return $this->uniqid;
2107
    }
2108
2109
    /**
2110
     * Returns the classname label.
2111
     *
2112
     * @return string the classname label
2113
     */
2114
    public function getClassnameLabel()
2115
    {
2116
        return $this->classnameLabel;
2117
    }
2118
2119
    public function getPersistentParameters()
2120
    {
2121
        $parameters = [];
2122
2123
        foreach ($this->getExtensions() as $extension) {
2124
            $params = $extension->getPersistentParameters($this);
2125
2126
            if (!\is_array($params)) {
2127
                throw new \RuntimeException(sprintf('The %s::getPersistentParameters must return an array', \get_class($extension)));
2128
            }
2129
2130
            $parameters = array_merge($parameters, $params);
2131
        }
2132
2133
        return $parameters;
2134
    }
2135
2136
    /**
2137
     * @param string $name
2138
     *
2139
     * @return mixed|null
2140
     */
2141
    public function getPersistentParameter($name)
2142
    {
2143
        $parameters = $this->getPersistentParameters();
2144
2145
        return $parameters[$name] ?? null;
2146
    }
2147
2148
    public function getBreadcrumbs($action)
2149
    {
2150
        @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...
2151
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2152
            ' Use Sonata\AdminBundle\Admin\BreadcrumbsBuilder::getBreadcrumbs instead.',
2153
            E_USER_DEPRECATED
2154
        );
2155
2156
        return $this->getBreadcrumbsBuilder()->getBreadcrumbs($this, $action);
2157
    }
2158
2159
    /**
2160
     * Generates the breadcrumbs array.
2161
     *
2162
     * Note: the method will be called by the top admin instance (parent => child)
2163
     *
2164
     * @param string $action
2165
     *
2166
     * @return array
2167
     */
2168
    public function buildBreadcrumbs($action, ?ItemInterface $menu = null)
2169
    {
2170
        @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...
2171
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.',
2172
            E_USER_DEPRECATED
2173
        );
2174
2175
        if (isset($this->breadcrumbs[$action])) {
2176
            return $this->breadcrumbs[$action];
2177
        }
2178
2179
        return $this->breadcrumbs[$action] = $this->getBreadcrumbsBuilder()
2180
            ->buildBreadcrumbs($this, $action, $menu);
2181
    }
2182
2183
    /**
2184
     * NEXT_MAJOR : remove this method.
2185
     *
2186
     * @return BreadcrumbsBuilderInterface
2187
     */
2188
    final public function getBreadcrumbsBuilder()
2189
    {
2190
        @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...
2191
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2192
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
2193
            E_USER_DEPRECATED
2194
        );
2195
        if (null === $this->breadcrumbsBuilder) {
2196
            $this->breadcrumbsBuilder = new BreadcrumbsBuilder(
2197
                $this->getConfigurationPool()->getContainer()->getParameter('sonata.admin.configuration.breadcrumbs')
2198
            );
2199
        }
2200
2201
        return $this->breadcrumbsBuilder;
2202
    }
2203
2204
    /**
2205
     * NEXT_MAJOR : remove this method.
2206
     *
2207
     * @return AbstractAdmin
2208
     */
2209
    final public function setBreadcrumbsBuilder(BreadcrumbsBuilderInterface $value)
2210
    {
2211
        @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...
2212
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2213
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
2214
            E_USER_DEPRECATED
2215
        );
2216
        $this->breadcrumbsBuilder = $value;
2217
2218
        return $this;
2219
    }
2220
2221
    public function setCurrentChild($currentChild)
2222
    {
2223
        $this->currentChild = $currentChild;
2224
    }
2225
2226
    /**
2227
     * NEXT_MAJOR: Remove this method.
2228
     *
2229
     * @deprecated since sonata-project/admin-bundle 3.65, to be removed in 4.0
2230
     */
2231
    public function getCurrentChild()
2232
    {
2233
        @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...
2234
            sprintf(
2235
                'The %s() method is deprecated since version 3.65 and will be removed in 4.0. Use %s::isCurrentChild() instead.',
2236
                __METHOD__,
2237
                __CLASS__
2238
            ),
2239
            E_USER_DEPRECATED
2240
        );
2241
2242
        return $this->currentChild;
2243
    }
2244
2245
    public function isCurrentChild(): bool
2246
    {
2247
        return $this->currentChild;
2248
    }
2249
2250
    /**
2251
     * Returns the current child admin instance.
2252
     *
2253
     * @return AdminInterface|null the current child admin instance
2254
     */
2255
    public function getCurrentChildAdmin()
2256
    {
2257
        foreach ($this->children as $children) {
2258
            if ($children->isCurrentChild()) {
2259
                return $children;
2260
            }
2261
        }
2262
2263
        return null;
2264
    }
2265
2266
    public function trans($id, array $parameters = [], $domain = null, $locale = null)
2267
    {
2268
        @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...
2269
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2270
            E_USER_DEPRECATED
2271
        );
2272
2273
        $domain = $domain ?: $this->getTranslationDomain();
2274
2275
        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...
2276
    }
2277
2278
    /**
2279
     * Translate a message id.
2280
     *
2281
     * NEXT_MAJOR: remove this method
2282
     *
2283
     * @param string      $id
2284
     * @param int         $count
2285
     * @param string|null $domain
2286
     * @param string|null $locale
2287
     *
2288
     * @return string the translated string
2289
     *
2290
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2291
     */
2292
    public function transChoice($id, $count, array $parameters = [], $domain = null, $locale = null)
2293
    {
2294
        @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...
2295
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2296
            E_USER_DEPRECATED
2297
        );
2298
2299
        $domain = $domain ?: $this->getTranslationDomain();
2300
2301
        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...
2302
    }
2303
2304
    public function setTranslationDomain($translationDomain)
2305
    {
2306
        $this->translationDomain = $translationDomain;
2307
    }
2308
2309
    public function getTranslationDomain()
2310
    {
2311
        return $this->translationDomain;
2312
    }
2313
2314
    /**
2315
     * {@inheritdoc}
2316
     *
2317
     * NEXT_MAJOR: remove this method
2318
     *
2319
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2320
     */
2321
    public function setTranslator(TranslatorInterface $translator)
2322
    {
2323
        $args = \func_get_args();
2324
        if (isset($args[1]) && $args[1]) {
2325
            @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...
2326
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2327
                E_USER_DEPRECATED
2328
            );
2329
        }
2330
2331
        $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...
2332
    }
2333
2334
    /**
2335
     * {@inheritdoc}
2336
     *
2337
     * NEXT_MAJOR: remove this method
2338
     *
2339
     * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
2340
     */
2341
    public function getTranslator()
2342
    {
2343
        @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...
2344
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2345
            E_USER_DEPRECATED
2346
        );
2347
2348
        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...
2349
    }
2350
2351
    public function getTranslationLabel($label, $context = '', $type = '')
2352
    {
2353
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2354
    }
2355
2356
    public function setRequest(Request $request)
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
2357
    {
2358
        $this->request = $request;
2359
2360
        foreach ($this->getChildren() as $children) {
2361
            $children->setRequest($request);
2362
        }
2363
    }
2364
2365
    public function getRequest()
2366
    {
2367
        if (!$this->request) {
2368
            // NEXT_MAJOR: Throw \LogicException instead.
2369
            throw new \RuntimeException('The Request object has not been set');
2370
        }
2371
2372
        return $this->request;
2373
    }
2374
2375
    public function hasRequest()
2376
    {
2377
        return null !== $this->request;
2378
    }
2379
2380
    public function setFormContractor(FormContractorInterface $formBuilder)
2381
    {
2382
        $this->formContractor = $formBuilder;
2383
    }
2384
2385
    /**
2386
     * @return FormContractorInterface
2387
     */
2388
    public function getFormContractor()
2389
    {
2390
        return $this->formContractor;
2391
    }
2392
2393
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder)
2394
    {
2395
        $this->datagridBuilder = $datagridBuilder;
2396
    }
2397
2398
    public function getDatagridBuilder()
2399
    {
2400
        return $this->datagridBuilder;
2401
    }
2402
2403
    public function setListBuilder(ListBuilderInterface $listBuilder)
2404
    {
2405
        $this->listBuilder = $listBuilder;
2406
    }
2407
2408
    public function getListBuilder()
2409
    {
2410
        return $this->listBuilder;
2411
    }
2412
2413
    public function setShowBuilder(ShowBuilderInterface $showBuilder)
2414
    {
2415
        $this->showBuilder = $showBuilder;
2416
    }
2417
2418
    /**
2419
     * @return ShowBuilderInterface
2420
     */
2421
    public function getShowBuilder()
2422
    {
2423
        return $this->showBuilder;
2424
    }
2425
2426
    public function setConfigurationPool(Pool $configurationPool)
2427
    {
2428
        $this->configurationPool = $configurationPool;
2429
    }
2430
2431
    /**
2432
     * @return Pool
2433
     */
2434
    public function getConfigurationPool()
2435
    {
2436
        return $this->configurationPool;
2437
    }
2438
2439
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator)
2440
    {
2441
        $this->routeGenerator = $routeGenerator;
2442
    }
2443
2444
    /**
2445
     * @return RouteGeneratorInterface
2446
     */
2447
    public function getRouteGenerator()
2448
    {
2449
        return $this->routeGenerator;
2450
    }
2451
2452
    public function getCode()
2453
    {
2454
        return $this->code;
2455
    }
2456
2457
    /**
2458
     * NEXT_MAJOR: Remove this function.
2459
     *
2460
     * @deprecated This method is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0
2461
     *
2462
     * @param string $baseCodeRoute
2463
     */
2464
    public function setBaseCodeRoute($baseCodeRoute)
2465
    {
2466
        @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...
2467
            'The '.__METHOD__.' is deprecated since 3.24 and will be removed in 4.0.',
2468
            E_USER_DEPRECATED
2469
        );
2470
2471
        $this->baseCodeRoute = $baseCodeRoute;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0

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

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

Loading history...
2472
    }
2473
2474
    public function getBaseCodeRoute()
2475
    {
2476
        // NEXT_MAJOR: Uncomment the following lines.
2477
        // if ($this->isChild()) {
2478
        //     return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2479
        // }
2480
        //
2481
        // return $this->getCode();
2482
2483
        // NEXT_MAJOR: Remove all the code below.
2484
        if ($this->isChild()) {
2485
            $parentCode = $this->getParent()->getCode();
2486
2487
            if ($this->getParent()->isChild()) {
2488
                $parentCode = $this->getParent()->getBaseCodeRoute();
2489
            }
2490
2491
            return $parentCode.'|'.$this->getCode();
2492
        }
2493
2494
        return $this->baseCodeRoute;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0

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

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

Loading history...
2495
    }
2496
2497
    public function getModelManager()
2498
    {
2499
        return $this->modelManager;
2500
    }
2501
2502
    public function setModelManager(ModelManagerInterface $modelManager)
2503
    {
2504
        $this->modelManager = $modelManager;
2505
    }
2506
2507
    public function getManagerType()
2508
    {
2509
        return $this->managerType;
2510
    }
2511
2512
    /**
2513
     * @param string $type
2514
     */
2515
    public function setManagerType($type)
2516
    {
2517
        $this->managerType = $type;
2518
    }
2519
2520
    public function getObjectIdentifier()
2521
    {
2522
        return $this->getCode();
2523
    }
2524
2525
    /**
2526
     * Set the roles and permissions per role.
2527
     */
2528
    public function setSecurityInformation(array $information)
2529
    {
2530
        $this->securityInformation = $information;
2531
    }
2532
2533
    public function getSecurityInformation()
2534
    {
2535
        return $this->securityInformation;
2536
    }
2537
2538
    /**
2539
     * Return the list of permissions the user should have in order to display the admin.
2540
     *
2541
     * @param string $context
2542
     *
2543
     * @return array
2544
     */
2545
    public function getPermissionsShow($context)
2546
    {
2547
        switch ($context) {
2548
            case self::CONTEXT_DASHBOARD:
2549
            case self::CONTEXT_MENU:
2550
            default:
2551
                return ['LIST'];
2552
        }
2553
    }
2554
2555
    public function showIn($context)
2556
    {
2557
        switch ($context) {
2558
            case self::CONTEXT_DASHBOARD:
2559
            case self::CONTEXT_MENU:
2560
            default:
2561
                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...
2562
        }
2563
    }
2564
2565
    public function createObjectSecurity($object)
2566
    {
2567
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2568
    }
2569
2570
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
2571
    {
2572
        $this->securityHandler = $securityHandler;
2573
    }
2574
2575
    public function getSecurityHandler()
2576
    {
2577
        return $this->securityHandler;
2578
    }
2579
2580
    public function isGranted($name, $object = null)
2581
    {
2582
        $objectRef = $object ? '/'.spl_object_hash($object).'#'.$this->id($object) : '';
2583
        $key = md5(json_encode($name).$objectRef);
2584
2585
        if (!\array_key_exists($key, $this->cacheIsGranted)) {
2586
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2587
        }
2588
2589
        return $this->cacheIsGranted[$key];
2590
    }
2591
2592
    public function getUrlSafeIdentifier($entity)
2593
    {
2594
        return $this->getModelManager()->getUrlSafeIdentifier($entity);
2595
    }
2596
2597
    public function getNormalizedIdentifier($entity)
2598
    {
2599
        return $this->getModelManager()->getNormalizedIdentifier($entity);
2600
    }
2601
2602
    public function id($entity)
2603
    {
2604
        return $this->getNormalizedIdentifier($entity);
2605
    }
2606
2607
    public function setValidator($validator)
2608
    {
2609
        // NEXT_MAJOR: Move ValidatorInterface check to method signature
2610
        if (!$validator instanceof ValidatorInterface) {
2611
            throw new \InvalidArgumentException(
2612
                'Argument 1 must be an instance of Symfony\Component\Validator\Validator\ValidatorInterface'
2613
            );
2614
        }
2615
2616
        $this->validator = $validator;
2617
    }
2618
2619
    public function getValidator()
2620
    {
2621
        return $this->validator;
2622
    }
2623
2624
    public function getShow()
2625
    {
2626
        $this->buildShow();
2627
2628
        return $this->show;
2629
    }
2630
2631
    public function setFormTheme(array $formTheme)
2632
    {
2633
        $this->formTheme = $formTheme;
2634
    }
2635
2636
    public function getFormTheme()
2637
    {
2638
        return $this->formTheme;
2639
    }
2640
2641
    public function setFilterTheme(array $filterTheme)
2642
    {
2643
        $this->filterTheme = $filterTheme;
2644
    }
2645
2646
    public function getFilterTheme()
2647
    {
2648
        return $this->filterTheme;
2649
    }
2650
2651
    public function addExtension(AdminExtensionInterface $extension)
2652
    {
2653
        $this->extensions[] = $extension;
2654
    }
2655
2656
    public function getExtensions()
2657
    {
2658
        return $this->extensions;
2659
    }
2660
2661
    public function setMenuFactory(FactoryInterface $menuFactory)
2662
    {
2663
        $this->menuFactory = $menuFactory;
2664
    }
2665
2666
    public function getMenuFactory()
2667
    {
2668
        return $this->menuFactory;
2669
    }
2670
2671
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder)
2672
    {
2673
        $this->routeBuilder = $routeBuilder;
2674
    }
2675
2676
    public function getRouteBuilder()
2677
    {
2678
        return $this->routeBuilder;
2679
    }
2680
2681
    public function toString($object)
2682
    {
2683
        if (!\is_object($object)) {
2684
            return '';
2685
        }
2686
2687
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2688
            return (string) $object;
2689
        }
2690
2691
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2692
    }
2693
2694
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy)
2695
    {
2696
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2697
    }
2698
2699
    public function getLabelTranslatorStrategy()
2700
    {
2701
        return $this->labelTranslatorStrategy;
2702
    }
2703
2704
    public function supportsPreviewMode()
2705
    {
2706
        return $this->supportsPreviewMode;
2707
    }
2708
2709
    /**
2710
     * NEXT_MAJOR: Remove this.
2711
     *
2712
     * @deprecated since sonata-project/admin-bundle 3.x, to be removed in 4.0.
2713
     *
2714
     * Set custom per page options.
2715
     */
2716
    public function setPerPageOptions(array $options)
2717
    {
2718
        @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...
2719
            'The method %s is deprecated since sonata-project/admin-bundle 3.x and will be removed in 4.0.',
2720
            __METHOD__
2721
        ), E_USER_DEPRECATED);
2722
2723
        $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...
2724
    }
2725
2726
    /**
2727
     * Returns predefined per page options.
2728
     *
2729
     * @return array
2730
     */
2731
    public function getPerPageOptions()
2732
    {
2733
        // NEXT_MAJOR: Remove this line and uncomment the following
2734
        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...
2735
//        $perPageOptions = $this->getModelManager()->getDefaultPerPageOptions($this->class);
2736
//        $perPageOptions[] = $this->getMaxPerPage();
2737
//
2738
//        $perPageOptions = array_unique($perPageOptions);
2739
//        sort($perPageOptions);
2740
//
2741
//        return $perPageOptions;
2742
    }
2743
2744
    /**
2745
     * Set pager type.
2746
     *
2747
     * @param string $pagerType
2748
     */
2749
    public function setPagerType($pagerType)
2750
    {
2751
        $this->pagerType = $pagerType;
2752
    }
2753
2754
    /**
2755
     * Get pager type.
2756
     *
2757
     * @return string
2758
     */
2759
    public function getPagerType()
2760
    {
2761
        return $this->pagerType;
2762
    }
2763
2764
    /**
2765
     * Returns true if the per page value is allowed, false otherwise.
2766
     *
2767
     * @param int $perPage
2768
     *
2769
     * @return bool
2770
     */
2771
    public function determinedPerPageValue($perPage)
2772
    {
2773
        return \in_array($perPage, $this->getPerPageOptions(), true);
2774
    }
2775
2776
    public function isAclEnabled()
2777
    {
2778
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2779
    }
2780
2781
    public function getObjectMetadata($object)
2782
    {
2783
        return new Metadata($this->toString($object));
2784
    }
2785
2786
    public function getListModes()
2787
    {
2788
        return $this->listModes;
2789
    }
2790
2791
    public function setListMode($mode)
2792
    {
2793
        if (!$this->hasRequest()) {
2794
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2795
        }
2796
2797
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2798
    }
2799
2800
    public function getListMode()
2801
    {
2802
        if (!$this->hasRequest()) {
2803
            return 'list';
2804
        }
2805
2806
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2807
    }
2808
2809
    public function getAccessMapping()
2810
    {
2811
        return $this->accessMapping;
2812
    }
2813
2814
    public function checkAccess($action, $object = null)
2815
    {
2816
        $access = $this->getAccess();
2817
2818
        if (!\array_key_exists($action, $access)) {
2819
            throw new \InvalidArgumentException(sprintf(
2820
                'Action "%s" could not be found in access mapping.'
2821
                .' Please make sure your action is defined into your admin class accessMapping property.',
2822
                $action
2823
            ));
2824
        }
2825
2826
        if (!\is_array($access[$action])) {
2827
            $access[$action] = [$access[$action]];
2828
        }
2829
2830
        foreach ($access[$action] as $role) {
2831
            if (false === $this->isGranted($role, $object)) {
2832
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2833
            }
2834
        }
2835
    }
2836
2837
    /**
2838
     * Hook to handle access authorization, without throw Exception.
2839
     *
2840
     * @param string $action
2841
     * @param object $object
2842
     *
2843
     * @return bool
2844
     */
2845
    public function hasAccess($action, $object = null)
2846
    {
2847
        $access = $this->getAccess();
2848
2849
        if (!\array_key_exists($action, $access)) {
2850
            return false;
2851
        }
2852
2853
        if (!\is_array($access[$action])) {
2854
            $access[$action] = [$access[$action]];
2855
        }
2856
2857
        foreach ($access[$action] as $role) {
2858
            if (false === $this->isGranted($role, $object)) {
2859
                return false;
2860
            }
2861
        }
2862
2863
        return true;
2864
    }
2865
2866
    /**
2867
     * @param string      $action
2868
     * @param object|null $object
2869
     *
2870
     * @return array
2871
     */
2872
    public function configureActionButtons($action, $object = null)
2873
    {
2874
        $list = [];
2875
2876
        if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true)
2877
            && $this->hasAccess('create')
2878
            && $this->hasRoute('create')
2879
        ) {
2880
            $list['create'] = [
2881
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2882
                'template' => $this->getTemplate('button_create'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

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

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

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

An additional type check may prevent trouble.

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

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

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

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

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

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

An additional type check may prevent trouble.

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

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

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

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

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

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

An additional type check may prevent trouble.

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

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

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

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

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

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

An additional type check may prevent trouble.

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

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

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

Loading history...
2929
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2930
            ];
2931
        }
2932
2933
        if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true)
2934
            && $this->hasAccess('list')
2935
            && $this->hasRoute('list')
2936
        ) {
2937
            $list['list'] = [
2938
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2939
                'template' => $this->getTemplate('button_list'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2940
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2941
            ];
2942
        }
2943
2944
        return $list;
2945
    }
2946
2947
    /**
2948
     * @param string $action
2949
     * @param object $object
2950
     *
2951
     * @return array
2952
     */
2953
    public function getActionButtons($action, $object = null)
2954
    {
2955
        $list = $this->configureActionButtons($action, $object);
2956
2957
        foreach ($this->getExtensions() as $extension) {
2958
            // NEXT_MAJOR: remove method check
2959
            if (method_exists($extension, 'configureActionButtons')) {
2960
                $list = $extension->configureActionButtons($this, $list, $action, $object);
2961
            }
2962
        }
2963
2964
        return $list;
2965
    }
2966
2967
    /**
2968
     * Get the list of actions that can be accessed directly from the dashboard.
2969
     *
2970
     * @return array
2971
     */
2972
    public function getDashboardActions()
2973
    {
2974
        $actions = [];
2975
2976
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2977
            $actions['create'] = [
2978
                'label' => 'link_add',
2979
                'translation_domain' => 'SonataAdminBundle',
2980
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2981
                'template' => $this->getTemplate('action_create'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
2982
                // 'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2983
                'url' => $this->generateUrl('create'),
2984
                'icon' => 'plus-circle',
2985
            ];
2986
        }
2987
2988
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2989
            $actions['list'] = [
2990
                'label' => 'link_list',
2991
                'translation_domain' => 'SonataAdminBundle',
2992
                'url' => $this->generateUrl('list'),
2993
                'icon' => 'list',
2994
            ];
2995
        }
2996
2997
        return $actions;
2998
    }
2999
3000
    /**
3001
     * Setting to true will enable mosaic button for the admin screen.
3002
     * Setting to false will hide mosaic button for the admin screen.
3003
     *
3004
     * @param bool $isShown
3005
     */
3006
    final public function showMosaicButton($isShown)
3007
    {
3008
        if ($isShown) {
3009
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
3010
        } else {
3011
            unset($this->listModes['mosaic']);
3012
        }
3013
    }
3014
3015
    /**
3016
     * @param object $object
3017
     */
3018
    final public function getSearchResultLink($object)
3019
    {
3020
        foreach ($this->searchResultActions as $action) {
3021
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
3022
                return $this->generateObjectUrl($action, $object);
3023
            }
3024
        }
3025
3026
        return null;
3027
    }
3028
3029
    /**
3030
     * Checks if a filter type is set to a default value.
3031
     *
3032
     * @param string $name
3033
     *
3034
     * @return bool
3035
     */
3036
    final public function isDefaultFilter($name)
3037
    {
3038
        $filter = $this->getFilterParameters();
3039
        $default = $this->getDefaultFilterValues();
3040
3041
        if (!\array_key_exists($name, $filter) || !\array_key_exists($name, $default)) {
3042
            return false;
3043
        }
3044
3045
        return $filter[$name] === $default[$name];
3046
    }
3047
3048
    /**
3049
     * Check object existence and access, without throw Exception.
3050
     *
3051
     * @param string $action
3052
     * @param object $object
3053
     *
3054
     * @return bool
3055
     */
3056
    public function canAccessObject($action, $object)
3057
    {
3058
        return $object && $this->id($object) && $this->hasAccess($action, $object);
3059
    }
3060
3061
    protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
3062
    {
3063
        return $query;
3064
    }
3065
3066
    /**
3067
     * @return MutableTemplateRegistryInterface
3068
     */
3069
    final protected function getTemplateRegistry()
3070
    {
3071
        return $this->templateRegistry;
3072
    }
3073
3074
    /**
3075
     * Returns a list of default sort values.
3076
     *
3077
     * @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...
3078
     */
3079
    final protected function getDefaultSortValues(): array
3080
    {
3081
        $defaultSortValues = [];
3082
3083
        $this->configureDefaultSortValues($defaultSortValues);
3084
3085
        foreach ($this->getExtensions() as $extension) {
3086
            // NEXT_MAJOR: remove method check
3087
            if (method_exists($extension, 'configureDefaultSortValues')) {
3088
                $extension->configureDefaultSortValues($this, $defaultSortValues);
3089
            }
3090
        }
3091
3092
        return $defaultSortValues;
3093
    }
3094
3095
    /**
3096
     * Returns a list of default filters.
3097
     *
3098
     * @return array
3099
     */
3100
    final protected function getDefaultFilterValues()
3101
    {
3102
        $defaultFilterValues = [];
3103
3104
        $this->configureDefaultFilterValues($defaultFilterValues);
3105
3106
        foreach ($this->getExtensions() as $extension) {
3107
            // NEXT_MAJOR: remove method check
3108
            if (method_exists($extension, 'configureDefaultFilterValues')) {
3109
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
3110
            }
3111
        }
3112
3113
        return $defaultFilterValues;
3114
    }
3115
3116
    protected function configureFormFields(FormMapper $form)
3117
    {
3118
    }
3119
3120
    protected function configureListFields(ListMapper $list)
3121
    {
3122
    }
3123
3124
    protected function configureDatagridFilters(DatagridMapper $filter)
3125
    {
3126
    }
3127
3128
    protected function configureShowFields(ShowMapper $show)
3129
    {
3130
    }
3131
3132
    protected function configureRoutes(RouteCollection $collection)
3133
    {
3134
    }
3135
3136
    /**
3137
     * Allows you to customize batch actions.
3138
     *
3139
     * @param array $actions List of actions
3140
     *
3141
     * @return array
3142
     */
3143
    protected function configureBatchActions($actions)
3144
    {
3145
        return $actions;
3146
    }
3147
3148
    /**
3149
     * NEXT_MAJOR: remove this method.
3150
     *
3151
     * @deprecated Use configureTabMenu instead
3152
     */
3153
    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...
3154
    {
3155
    }
3156
3157
    /**
3158
     * Configures the tab menu in your admin.
3159
     *
3160
     * @param string $action
3161
     */
3162
    protected function configureTabMenu(ItemInterface $menu, $action, ?AdminInterface $childAdmin = null)
3163
    {
3164
        // Use configureSideMenu not to mess with previous overrides
3165
        // NEXT_MAJOR: remove this line
3166
        $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...
3167
    }
3168
3169
    /**
3170
     * build the view FieldDescription array.
3171
     */
3172
    protected function buildShow()
3173
    {
3174
        if ($this->show) {
3175
            return;
3176
        }
3177
3178
        $this->show = new FieldDescriptionCollection();
3179
        $mapper = new ShowMapper($this->showBuilder, $this->show, $this);
3180
3181
        $this->configureShowFields($mapper);
3182
3183
        foreach ($this->getExtensions() as $extension) {
3184
            $extension->configureShowFields($mapper);
3185
        }
3186
    }
3187
3188
    /**
3189
     * build the list FieldDescription array.
3190
     */
3191
    protected function buildList()
3192
    {
3193
        if ($this->list) {
3194
            return;
3195
        }
3196
3197
        $this->list = $this->getListBuilder()->getBaseList();
3198
3199
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
3200
3201
        if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
3202
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3203
                $this->getClass(),
3204
                'batch',
3205
                [
3206
                    'label' => 'batch',
3207
                    'code' => '_batch',
3208
                    'sortable' => false,
3209
                    'virtual_field' => true,
3210
                ]
3211
            );
3212
3213
            $fieldDescription->setAdmin($this);
3214
            // NEXT_MAJOR: Remove this line and use commented line below it instead
3215
            $fieldDescription->setTemplate($this->getTemplate('batch'));
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
3216
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
3217
3218
            $mapper->add($fieldDescription, 'batch');
3219
        }
3220
3221
        $this->configureListFields($mapper);
3222
3223
        foreach ($this->getExtensions() as $extension) {
3224
            $extension->configureListFields($mapper);
3225
        }
3226
3227
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
3228
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3229
                $this->getClass(),
3230
                'select',
3231
                [
3232
                    'label' => false,
3233
                    'code' => '_select',
3234
                    'sortable' => false,
3235
                    'virtual_field' => false,
3236
                ]
3237
            );
3238
3239
            $fieldDescription->setAdmin($this);
3240
            // NEXT_MAJOR: Remove this line and use commented line below it instead
3241
            $fieldDescription->setTemplate($this->getTemplate('select'));
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

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

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

Loading history...
3242
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
3243
3244
            $mapper->add($fieldDescription, 'select');
3245
        }
3246
    }
3247
3248
    /**
3249
     * Build the form FieldDescription collection.
3250
     */
3251
    protected function buildForm()
3252
    {
3253
        if ($this->form) {
3254
            return;
3255
        }
3256
3257
        // append parent object if any
3258
        // todo : clean the way the Admin class can retrieve set the object
3259
        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...
3260
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
3261
3262
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3263
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3264
3265
            $object = $this->getSubject();
3266
3267
            $value = $propertyAccessor->getValue($object, $propertyPath);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 3265 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...
3268
3269
            if (\is_array($value) || $value instanceof \ArrayAccess) {
3270
                $value[] = $parent;
3271
                $propertyAccessor->setValue($object, $propertyPath, $value);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 3265 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...
3272
            } else {
3273
                $propertyAccessor->setValue($object, $propertyPath, $parent);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getSubject() on line 3265 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...
3274
            }
3275
        }
3276
3277
        $formBuilder = $this->getFormBuilder();
3278
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
3279
            $this->preValidate($event->getData());
3280
        }, 100);
3281
3282
        $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...
3283
    }
3284
3285
    /**
3286
     * Gets the subclass corresponding to the given name.
3287
     *
3288
     * @param string $name The name of the sub class
3289
     *
3290
     * @return string the subclass
3291
     */
3292
    protected function getSubClass($name)
3293
    {
3294
        if ($this->hasSubClass($name)) {
3295
            return $this->subClasses[$name];
3296
        }
3297
3298
        // NEXT_MAJOR: Throw \LogicException instead.
3299
        throw new \RuntimeException(sprintf(
3300
            'Unable to find the subclass `%s` for admin `%s`',
3301
            $name,
3302
            static::class
3303
        ));
3304
    }
3305
3306
    /**
3307
     * Attach the inline validator to the model metadata, this must be done once per admin.
3308
     */
3309
    protected function attachInlineValidator()
3310
    {
3311
        $admin = $this;
3312
3313
        // add the custom inline validation option
3314
        $metadata = $this->validator->getMetadataFor($this->getClass());
3315
3316
        $metadata->addConstraint(new InlineConstraint([
3317
            'service' => $this,
3318
            'method' => static function (ErrorElement $errorElement, $object) use ($admin) {
3319
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
3320
3321
                // This avoid the main validation to be cascaded to children
3322
                // The problem occurs when a model Page has a collection of Page as property
3323
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
3324
                    return;
3325
                }
3326
3327
                $admin->validate($errorElement, $object);
3328
3329
                foreach ($admin->getExtensions() as $extension) {
3330
                    $extension->validate($admin, $errorElement, $object);
3331
                }
3332
            },
3333
            'serializingWarning' => true,
3334
        ]));
3335
    }
3336
3337
    /**
3338
     * NEXT_MAJOR: Remove this function.
3339
     *
3340
     * @deprecated since sonata-project/admin-bundle 3.x, to be removed in 4.0.
3341
     *
3342
     * Predefine per page options.
3343
     */
3344
    protected function predefinePerPageOptions()
3345
    {
3346
        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...
3347
        $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...
3348
        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...
3349
    }
3350
3351
    /**
3352
     * Return list routes with permissions name.
3353
     *
3354
     * @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...
3355
     */
3356
    protected function getAccess()
3357
    {
3358
        $access = array_merge([
3359
            'acl' => 'MASTER',
3360
            'export' => 'EXPORT',
3361
            'historyCompareRevisions' => 'EDIT',
3362
            'historyViewRevision' => 'EDIT',
3363
            'history' => 'EDIT',
3364
            'edit' => 'EDIT',
3365
            'show' => 'VIEW',
3366
            'create' => 'CREATE',
3367
            'delete' => 'DELETE',
3368
            'batchDelete' => 'DELETE',
3369
            'list' => 'LIST',
3370
        ], $this->getAccessMapping());
3371
3372
        foreach ($this->extensions as $extension) {
3373
            // NEXT_MAJOR: remove method check
3374
            if (method_exists($extension, 'getAccessMapping')) {
3375
                $access = array_merge($access, $extension->getAccessMapping($this));
3376
            }
3377
        }
3378
3379
        return $access;
3380
    }
3381
3382
    /**
3383
     * Configures a list of default filters.
3384
     */
3385
    protected function configureDefaultFilterValues(array &$filterValues)
3386
    {
3387
    }
3388
3389
    /**
3390
     * Configures a list of default sort values.
3391
     *
3392
     * Example:
3393
     *   $sortValues['_sort_by'] = 'foo'
3394
     *   $sortValues['_sort_order'] = 'DESC'
3395
     */
3396
    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...
3397
    {
3398
    }
3399
3400
    /**
3401
     * Build all the related urls to the current admin.
3402
     */
3403
    private function buildRoutes(): void
3404
    {
3405
        if ($this->loaded['routes']) {
3406
            return;
3407
        }
3408
3409
        $this->loaded['routes'] = true;
3410
3411
        $this->routes = new RouteCollection(
3412
            $this->getBaseCodeRoute(),
3413
            $this->getBaseRouteName(),
3414
            $this->getBaseRoutePattern(),
3415
            $this->getBaseControllerName()
3416
        );
3417
3418
        $this->routeBuilder->build($this, $this->routes);
3419
3420
        $this->configureRoutes($this->routes);
3421
3422
        foreach ($this->getExtensions() as $extension) {
3423
            $extension->configureRoutes($this, $this->routes);
3424
        }
3425
    }
3426
}
3427
3428
class_exists(\Sonata\Form\Validator\ErrorElement::class);
3429