Completed
Pull Request — master (#4788)
by Jordi Sala
08:56
created

AbstractAdmin::getSubject()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
470
471
    /**
472
     * The cached base route name.
473
     *
474
     * @var string
475
     */
476
    private $cachedBaseRouteName;
477
478
    /**
479
     * The cached base route pattern.
480
     *
481
     * @var string
482
     */
483
    private $cachedBaseRoutePattern;
484
485
    /**
486
     * The form group disposition.
487
     *
488
     * @var array|bool
489
     */
490
    private $formGroups = false;
491
492
    /**
493
     * The form tabs disposition.
494
     *
495
     * @var array|bool
496
     */
497
    private $formTabs = false;
498
499
    /**
500
     * The view group disposition.
501
     *
502
     * @var array|bool
503
     */
504
    private $showGroups = false;
505
506
    /**
507
     * The view tab disposition.
508
     *
509
     * @var array|bool
510
     */
511
    private $showTabs = false;
512
513
    /**
514
     * The manager type to use for the admin.
515
     *
516
     * @var string
517
     */
518
    private $managerType;
519
520
    /**
521
     * @param string $code
522
     * @param string $class
523
     * @param string $baseControllerName
524
     */
525
    public function __construct($code, $class, $baseControllerName)
526
    {
527
        $this->code = $code;
528
        $this->class = $class;
529
        $this->baseControllerName = $baseControllerName;
530
531
        $this->predefinePerPageOptions();
532
        $this->datagridValues['_per_page'] = $this->maxPerPage;
533
    }
534
535
    /**
536
     * {@inheritdoc}
537
     *
538
     * NEXT_MAJOR: return null to indicate no override
539
     */
540
    public function getExportFormats()
541
    {
542
        return [
543
            'json', 'xml', 'csv', 'xls',
544
        ];
545
    }
546
547
    /**
548
     * {@inheritdoc}
549
     */
550
    public function getExportFields()
551
    {
552
        $fields = $this->getModelManager()->getExportFields($this->getClass());
553
554
        foreach ($this->getExtensions() as $extension) {
555
            if (method_exists($extension, 'configureExportFields')) {
556
                $fields = $extension->configureExportFields($this, $fields);
557
            }
558
        }
559
560
        return $fields;
561
    }
562
563
    /**
564
     * {@inheritdoc}
565
     */
566
    public function getDataSourceIterator()
567
    {
568
        $datagrid = $this->getDatagrid();
569
        $datagrid->buildPager();
570
571
        $fields = [];
572
573
        foreach ($this->getExportFields() as $key => $field) {
574
            $label = $this->getTranslationLabel($field, 'export', 'label');
575
            $transLabel = $this->trans($label);
576
577
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
578
            // No translation key exists
579
            if ($transLabel == $label) {
580
                $fields[$key] = $field;
581
            } else {
582
                $fields[$transLabel] = $field;
583
            }
584
        }
585
586
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
587
    }
588
589
    /**
590
     * {@inheritdoc}
591
     */
592
    public function validate(ErrorElement $errorElement, $object)
593
    {
594
    }
595
596
    /**
597
     * define custom variable.
598
     */
599
    public function initialize()
600
    {
601
        if (!$this->classnameLabel) {
602
            $this->classnameLabel = substr($this->getClass(), strrpos($this->getClass(), '\\') + 1);
603
        }
604
605
        $this->configure();
606
    }
607
608
    /**
609
     * {@inheritdoc}
610
     */
611
    public function update($object)
612
    {
613
        $this->preUpdate($object);
614
        foreach ($this->extensions as $extension) {
615
            $extension->preUpdate($this, $object);
616
        }
617
618
        $result = $this->getModelManager()->update($object);
619
        // BC compatibility
620
        if (null !== $result) {
621
            $object = $result;
622
        }
623
624
        $this->postUpdate($object);
625
        foreach ($this->extensions as $extension) {
626
            $extension->postUpdate($this, $object);
627
        }
628
629
        return $object;
630
    }
631
632
    /**
633
     * {@inheritdoc}
634
     */
635
    public function create($object)
636
    {
637
        $this->prePersist($object);
638
        foreach ($this->extensions as $extension) {
639
            $extension->prePersist($this, $object);
640
        }
641
642
        $result = $this->getModelManager()->create($object);
643
        // BC compatibility
644
        if (null !== $result) {
645
            $object = $result;
646
        }
647
648
        $this->postPersist($object);
649
        foreach ($this->extensions as $extension) {
650
            $extension->postPersist($this, $object);
651
        }
652
653
        $this->createObjectSecurity($object);
654
655
        return $object;
656
    }
657
658
    /**
659
     * {@inheritdoc}
660
     */
661
    public function delete($object)
662
    {
663
        $this->preRemove($object);
664
        foreach ($this->extensions as $extension) {
665
            $extension->preRemove($this, $object);
666
        }
667
668
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
669
        $this->getModelManager()->delete($object);
670
671
        $this->postRemove($object);
672
        foreach ($this->extensions as $extension) {
673
            $extension->postRemove($this, $object);
674
        }
675
    }
676
677
    /**
678
     * {@inheritdoc}
679
     */
680
    public function preValidate($object)
681
    {
682
    }
683
684
    /**
685
     * {@inheritdoc}
686
     */
687
    public function preUpdate($object)
688
    {
689
    }
690
691
    /**
692
     * {@inheritdoc}
693
     */
694
    public function postUpdate($object)
695
    {
696
    }
697
698
    /**
699
     * {@inheritdoc}
700
     */
701
    public function prePersist($object)
702
    {
703
    }
704
705
    /**
706
     * {@inheritdoc}
707
     */
708
    public function postPersist($object)
709
    {
710
    }
711
712
    /**
713
     * {@inheritdoc}
714
     */
715
    public function preRemove($object)
716
    {
717
    }
718
719
    /**
720
     * {@inheritdoc}
721
     */
722
    public function postRemove($object)
723
    {
724
    }
725
726
    /**
727
     * {@inheritdoc}
728
     */
729
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
730
    {
731
    }
732
733
    /**
734
     * {@inheritdoc}
735
     */
736
    public function getFilterParameters()
737
    {
738
        $parameters = [];
739
740
        // build the values array
741
        if ($this->hasRequest()) {
742
            $filters = $this->request->query->get('filter', []);
743
744
            // if persisting filters, save filters to session, or pull them out of session if no new filters set
745
            if ($this->persistFilters) {
746
                if ($filters == [] && 'reset' != $this->request->query->get('filters')) {
747
                    $filters = $this->request->getSession()->get($this->getCode().'.filter.parameters', []);
748
                } else {
749
                    $this->request->getSession()->set($this->getCode().'.filter.parameters', $filters);
750
                }
751
            }
752
753
            $parameters = array_merge(
754
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
755
                $this->datagridValues,
756
                $this->getDefaultFilterValues(),
757
                $filters
758
            );
759
760
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
761
                $parameters['_per_page'] = $this->maxPerPage;
762
            }
763
764
            // always force the parent value
765
            if ($this->isChild() && $this->getParentAssociationMapping()) {
766
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
767
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
768
            }
769
        }
770
771
        return $parameters;
772
    }
773
774
    /**
775
     * Returns the name of the parent related field, so the field can be use to set the default
776
     * value (ie the parent object) or to filter the object.
777
     *
778
     * @return string the name of the parent related field
779
     */
780
    public function getParentAssociationMapping()
781
    {
782
        return $this->parentAssociationMapping;
783
    }
784
785
    /**
786
     * Returns the baseRoutePattern used to generate the routing information.
787
     *
788
     * @throws \RuntimeException
789
     *
790
     * @return string the baseRoutePattern used to generate the routing information
791
     */
792
    public function getBaseRoutePattern()
793
    {
794
        if (null !== $this->cachedBaseRoutePattern) {
795
            return $this->cachedBaseRoutePattern;
796
        }
797
798
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
799
            if (!$this->baseRoutePattern) {
800
                preg_match(self::CLASS_REGEX, $this->class, $matches);
801
802
                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...
803
                    throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
804
                }
805
            }
806
807
            $this->cachedBaseRoutePattern = sprintf('%s/%s/%s',
808
                $this->getParent()->getBaseRoutePattern(),
809
                $this->getParent()->getRouterIdParameter(),
810
                $this->baseRoutePattern ?: $this->urlize($matches[5], '-')
0 ignored issues
show
Bug introduced by
The variable $matches does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
811
            );
812
        } elseif ($this->baseRoutePattern) {
813
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
814
        } else {
815
            preg_match(self::CLASS_REGEX, $this->class, $matches);
816
817
            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...
818
                throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
819
            }
820
821
            $this->cachedBaseRoutePattern = sprintf('/%s%s/%s',
822
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
823
                $this->urlize($matches[3], '-'),
824
                $this->urlize($matches[5], '-')
825
            );
826
        }
827
828
        return $this->cachedBaseRoutePattern;
829
    }
830
831
    /**
832
     * Returns the baseRouteName used to generate the routing information.
833
     *
834
     * @throws \RuntimeException
835
     *
836
     * @return string the baseRouteName used to generate the routing information
837
     */
838
    public function getBaseRouteName()
839
    {
840
        if (null !== $this->cachedBaseRouteName) {
841
            return $this->cachedBaseRouteName;
842
        }
843
844
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
845
            if (!$this->baseRouteName) {
846
                preg_match(self::CLASS_REGEX, $this->class, $matches);
847
848
                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...
849
                    throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
850
                }
851
            }
852
853
            $this->cachedBaseRouteName = sprintf('%s_%s',
854
                $this->getParent()->getBaseRouteName(),
855
                $this->baseRouteName ?: $this->urlize($matches[5])
0 ignored issues
show
Bug introduced by
The variable $matches does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
856
            );
857
        } elseif ($this->baseRouteName) {
858
            $this->cachedBaseRouteName = $this->baseRouteName;
859
        } else {
860
            preg_match(self::CLASS_REGEX, $this->class, $matches);
861
862
            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...
863
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
864
            }
865
866
            $this->cachedBaseRouteName = sprintf('admin_%s%s_%s',
867
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
868
                $this->urlize($matches[3]),
869
                $this->urlize($matches[5])
870
            );
871
        }
872
873
        return $this->cachedBaseRouteName;
874
    }
875
876
    /**
877
     * {@inheritdoc}
878
     */
879
    public function getClass()
880
    {
881
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
882
        if ($this->hasSubject() && is_object($this->getSubject())) {
883
            return ClassUtils::getClass($this->getSubject());
884
        }
885
886
        if (!$this->hasActiveSubClass()) {
887
            if (count($this->getSubClasses()) > 0) {
888
                $subject = $this->getSubject();
889
890
                if ($subject && is_object($subject)) {
891
                    return ClassUtils::getClass($subject);
892
                }
893
            }
894
895
            return $this->class;
896
        }
897
898
        if ($this->getParentFieldDescription() && $this->hasActiveSubClass()) {
899
            throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
900
        }
901
902
        $subClass = $this->getRequest()->query->get('subclass');
903
904
        return $this->getSubClass($subClass);
905
    }
906
907
    /**
908
     * {@inheritdoc}
909
     */
910
    public function getSubClasses()
911
    {
912
        return $this->subClasses;
913
    }
914
915
    /**
916
     * {@inheritdoc}
917
     */
918
    public function addSubClass($subClass)
919
    {
920
        if (!in_array($subClass, $this->subClasses)) {
921
            $this->subClasses[] = $subClass;
922
        }
923
    }
924
925
    /**
926
     * {@inheritdoc}
927
     */
928
    public function setSubClasses(array $subClasses)
929
    {
930
        $this->subClasses = $subClasses;
931
    }
932
933
    /**
934
     * {@inheritdoc}
935
     */
936
    public function hasSubClass($name)
937
    {
938
        return isset($this->subClasses[$name]);
939
    }
940
941
    /**
942
     * {@inheritdoc}
943
     */
944
    public function hasActiveSubClass()
945
    {
946
        if (count($this->subClasses) > 0 && $this->request) {
947
            return null !== $this->getRequest()->query->get('subclass');
948
        }
949
950
        return false;
951
    }
952
953
    /**
954
     * {@inheritdoc}
955
     */
956
    public function getActiveSubClass()
957
    {
958
        if (!$this->hasActiveSubClass()) {
959
            return;
960
        }
961
962
        return $this->getClass();
963
    }
964
965
    /**
966
     * {@inheritdoc}
967
     */
968
    public function getActiveSubclassCode()
969
    {
970
        if (!$this->hasActiveSubClass()) {
971
            return;
972
        }
973
974
        $subClass = $this->getRequest()->query->get('subclass');
975
976
        if (!$this->hasSubClass($subClass)) {
977
            return;
978
        }
979
980
        return $subClass;
981
    }
982
983
    /**
984
     * {@inheritdoc}
985
     */
986
    final public function getBatchActions()
987
    {
988
        $actions = [];
989
990
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
991
            $actions['delete'] = [
992
                'label' => 'action_delete',
993
                'translation_domain' => 'SonataAdminBundle',
994
                'ask_confirmation' => true, // by default always true
995
            ];
996
        }
997
998
        $actions = $this->configureBatchActions($actions);
999
1000
        foreach ($this->getExtensions() as $extension) {
1001
            $actions = $extension->configureBatchActions($this, $actions);
1002
        }
1003
1004
        foreach ($actions  as $name => &$action) {
1005
            if (!array_key_exists('label', $action)) {
1006
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1007
            }
1008
1009
            if (!array_key_exists('translation_domain', $action)) {
1010
                $action['translation_domain'] = $this->getTranslationDomain();
1011
            }
1012
        }
1013
1014
        return $actions;
1015
    }
1016
1017
    /**
1018
     * {@inheritdoc}
1019
     */
1020
    public function getRoutes()
1021
    {
1022
        $this->buildRoutes();
1023
1024
        return $this->routes;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->routes; (Sonata\AdminBundle\Route\RouteCollection) is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...torInterface::getRoutes of type Sonata\AdminBundle\Admin\RouteCollection.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1025
    }
1026
1027
    /**
1028
     * {@inheritdoc}
1029
     */
1030
    public function getRouterIdParameter()
1031
    {
1032
        return '{'.$this->getIdParameter().'}';
1033
    }
1034
1035
    /**
1036
     * {@inheritdoc}
1037
     */
1038
    public function getIdParameter()
1039
    {
1040
        $parameter = 'id';
1041
1042
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1043
            $parameter = 'child'.ucfirst($parameter);
1044
        }
1045
1046
        return $parameter;
1047
    }
1048
1049
    /**
1050
     * {@inheritdoc}
1051
     */
1052
    public function hasRoute($name)
1053
    {
1054
        if (!$this->routeGenerator) {
1055
            throw new \RuntimeException('RouteGenerator cannot be null');
1056
        }
1057
1058
        return $this->routeGenerator->hasAdminRoute($this, $name);
1059
    }
1060
1061
    /**
1062
     * {@inheritdoc}
1063
     */
1064
    public function isCurrentRoute($name, $adminCode = null)
1065
    {
1066
        if (!$this->hasRequest()) {
1067
            return false;
1068
        }
1069
1070
        $request = $this->getRequest();
1071
        $route = $request->get('_route');
1072
1073
        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...
1074
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1075
        } else {
1076
            $admin = $this;
1077
        }
1078
1079
        if (!$admin) {
1080
            return false;
1081
        }
1082
1083
        return ($admin->getBaseRouteName().'_'.$name) == $route;
1084
    }
1085
1086
    /**
1087
     * {@inheritdoc}
1088
     */
1089
    public function generateObjectUrl($name, $object, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1090
    {
1091
        $parameters['id'] = $this->getUrlsafeIdentifier($object);
1092
1093
        return $this->generateUrl($name, $parameters, $absolute);
1094
    }
1095
1096
    /**
1097
     * {@inheritdoc}
1098
     */
1099
    public function generateUrl($name, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1100
    {
1101
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $absolute);
1102
    }
1103
1104
    /**
1105
     * {@inheritdoc}
1106
     */
1107
    public function generateMenuUrl($name, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1108
    {
1109
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $absolute);
1110
    }
1111
1112
    /**
1113
     * {@inheritdoc}
1114
     */
1115
    public function setTemplates(array $templates)
1116
    {
1117
        $this->templates = $templates;
1118
    }
1119
1120
    /**
1121
     * {@inheritdoc}
1122
     */
1123
    public function setTemplate($name, $template)
1124
    {
1125
        $this->templates[$name] = $template;
1126
    }
1127
1128
    /**
1129
     * {@inheritdoc}
1130
     */
1131
    public function getTemplates()
1132
    {
1133
        return $this->templates;
1134
    }
1135
1136
    /**
1137
     * {@inheritdoc}
1138
     */
1139
    public function getTemplate($name)
1140
    {
1141
        if (isset($this->templates[$name])) {
1142
            return $this->templates[$name];
1143
        }
1144
    }
1145
1146
    /**
1147
     * {@inheritdoc}
1148
     */
1149
    public function getNewInstance()
1150
    {
1151
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1152
        foreach ($this->getExtensions() as $extension) {
1153
            $extension->alterNewInstance($this, $object);
1154
        }
1155
1156
        return $object;
1157
    }
1158
1159
    /**
1160
     * {@inheritdoc}
1161
     */
1162
    public function getFormBuilder()
1163
    {
1164
        $this->formOptions['data_class'] = $this->getClass();
1165
1166
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1167
            $this->getUniqid(),
1168
            $this->formOptions
1169
        );
1170
1171
        $this->defineFormBuilder($formBuilder);
1172
1173
        return $formBuilder;
1174
    }
1175
1176
    /**
1177
     * This method is being called by the main admin class and the child class,
1178
     * the getFormBuilder is only call by the main admin class.
1179
     *
1180
     * @param FormBuilderInterface $formBuilder
1181
     */
1182
    public function defineFormBuilder(FormBuilderInterface $formBuilder)
1183
    {
1184
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1185
1186
        $this->configureFormFields($mapper);
1187
1188
        foreach ($this->getExtensions() as $extension) {
1189
            $extension->configureFormFields($mapper);
1190
        }
1191
1192
        $this->attachInlineValidator();
1193
    }
1194
1195
    /**
1196
     * {@inheritdoc}
1197
     */
1198
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
1199
    {
1200
        $pool = $this->getConfigurationPool();
1201
1202
        $adminCode = $fieldDescription->getOption('admin_code');
1203
1204
        if (null !== $adminCode) {
1205
            $admin = $pool->getAdminByAdminCode($adminCode);
0 ignored issues
show
Documentation introduced by
$adminCode is of type array, 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...
1206
        } else {
1207
            $admin = $pool->getAdminByClass($fieldDescription->getTargetEntity());
1208
        }
1209
1210
        if (!$admin) {
1211
            return;
1212
        }
1213
1214
        if ($this->hasRequest()) {
1215
            $admin->setRequest($this->getRequest());
1216
        }
1217
1218
        $fieldDescription->setAssociationAdmin($admin);
1219
    }
1220
1221
    /**
1222
     * {@inheritdoc}
1223
     */
1224
    public function getObject($id)
1225
    {
1226
        $object = $this->getModelManager()->find($this->getClass(), $id);
1227
        foreach ($this->getExtensions() as $extension) {
1228
            $extension->alterObject($this, $object);
1229
        }
1230
1231
        return $object;
1232
    }
1233
1234
    /**
1235
     * {@inheritdoc}
1236
     */
1237
    public function getForm()
1238
    {
1239
        $this->buildForm();
1240
1241
        return $this->form;
1242
    }
1243
1244
    /**
1245
     * {@inheritdoc}
1246
     */
1247
    public function getList()
1248
    {
1249
        $this->buildList();
1250
1251
        return $this->list;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->list; (array) is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...istryInterface::getList of type Sonata\AdminBundle\Admin...ldDescriptionCollection.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1252
    }
1253
1254
    /**
1255
     * {@inheritdoc}
1256
     */
1257
    public function createQuery($context = 'list')
1258
    {
1259
        if (func_num_args() > 0) {
1260
            @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...
1261
                'The $context argument of '.__METHOD__.' is deprecated since 3.3, to be removed in 4.0.',
1262
                E_USER_DEPRECATED
1263
            );
1264
        }
1265
        $query = $this->getModelManager()->createQuery($this->class);
1266
1267
        foreach ($this->extensions as $extension) {
1268
            $extension->configureQuery($this, $query, $context);
1269
        }
1270
1271
        return $query;
1272
    }
1273
1274
    /**
1275
     * {@inheritdoc}
1276
     */
1277
    public function getDatagrid()
1278
    {
1279
        $this->buildDatagrid();
1280
1281
        return $this->datagrid;
1282
    }
1283
1284
    /**
1285
     * {@inheritdoc}
1286
     */
1287
    public function buildTabMenu($action, AdminInterface $childAdmin = null)
1288
    {
1289
        if ($this->loaded['tab_menu']) {
1290
            return;
1291
        }
1292
1293
        $this->loaded['tab_menu'] = true;
1294
1295
        $menu = $this->menuFactory->createItem('root');
1296
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1297
        $menu->setExtra('translation_domain', $this->translationDomain);
1298
1299
        // Prevents BC break with KnpMenuBundle v1.x
1300
        if (method_exists($menu, 'setCurrentUri')) {
1301
            $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...
1302
        }
1303
1304
        $this->configureTabMenu($menu, $action, $childAdmin);
1305
1306
        foreach ($this->getExtensions() as $extension) {
1307
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1308
        }
1309
1310
        $this->menu = $menu;
1311
    }
1312
1313
    /**
1314
     * @param string         $action
1315
     * @param AdminInterface $childAdmin
1316
     *
1317
     * @return ItemInterface
1318
     */
1319
    public function getSideMenu($action, AdminInterface $childAdmin = null)
1320
    {
1321
        if ($this->isChild()) {
1322
            return $this->getParent()->getSideMenu($action, $this);
1323
        }
1324
1325
        $this->buildTabMenu($action, $childAdmin);
1326
1327
        return $this->menu;
1328
    }
1329
1330
    /**
1331
     * Returns the root code.
1332
     *
1333
     * @return string the root code
1334
     */
1335
    public function getRootCode()
1336
    {
1337
        return $this->getRoot()->getCode();
1338
    }
1339
1340
    /**
1341
     * Returns the master admin.
1342
     *
1343
     * @return AbstractAdmin the root admin class
1344
     */
1345
    public function getRoot()
1346
    {
1347
        $parentFieldDescription = $this->getParentFieldDescription();
1348
1349
        if (!$parentFieldDescription) {
1350
            return $this;
1351
        }
1352
1353
        return $parentFieldDescription->getAdmin()->getRoot();
1354
    }
1355
1356
    /**
1357
     * {@inheritdoc}
1358
     */
1359
    public function setBaseControllerName($baseControllerName)
1360
    {
1361
        $this->baseControllerName = $baseControllerName;
1362
    }
1363
1364
    /**
1365
     * {@inheritdoc}
1366
     */
1367
    public function getBaseControllerName()
1368
    {
1369
        return $this->baseControllerName;
1370
    }
1371
1372
    /**
1373
     * @param string $label
1374
     */
1375
    public function setLabel($label)
1376
    {
1377
        $this->label = $label;
1378
    }
1379
1380
    /**
1381
     * {@inheritdoc}
1382
     */
1383
    public function getLabel()
1384
    {
1385
        return $this->label;
1386
    }
1387
1388
    /**
1389
     * @param bool $persist
1390
     */
1391
    public function setPersistFilters($persist)
1392
    {
1393
        $this->persistFilters = $persist;
1394
    }
1395
1396
    /**
1397
     * @param int $maxPerPage
1398
     */
1399
    public function setMaxPerPage($maxPerPage)
1400
    {
1401
        $this->maxPerPage = $maxPerPage;
1402
    }
1403
1404
    /**
1405
     * @return int
1406
     */
1407
    public function getMaxPerPage()
1408
    {
1409
        return $this->maxPerPage;
1410
    }
1411
1412
    /**
1413
     * @param int $maxPageLinks
1414
     */
1415
    public function setMaxPageLinks($maxPageLinks)
1416
    {
1417
        $this->maxPageLinks = $maxPageLinks;
1418
    }
1419
1420
    /**
1421
     * @return int
1422
     */
1423
    public function getMaxPageLinks()
1424
    {
1425
        return $this->maxPageLinks;
1426
    }
1427
1428
    /**
1429
     * {@inheritdoc}
1430
     */
1431
    public function getFormGroups()
1432
    {
1433
        return $this->formGroups;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->formGroups; of type array|boolean adds the type boolean to the return on line 1433 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getFormGroups of type array.
Loading history...
1434
    }
1435
1436
    /**
1437
     * {@inheritdoc}
1438
     */
1439
    public function setFormGroups(array $formGroups)
1440
    {
1441
        $this->formGroups = $formGroups;
1442
    }
1443
1444
    /**
1445
     * {@inheritdoc}
1446
     */
1447
    public function removeFieldFromFormGroup($key)
1448
    {
1449
        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...
1450
            unset($this->formGroups[$name]['fields'][$key]);
1451
1452
            if (empty($this->formGroups[$name]['fields'])) {
1453
                unset($this->formGroups[$name]);
1454
            }
1455
        }
1456
    }
1457
1458
    /**
1459
     * @param array $group
1460
     * @param array $keys
1461
     */
1462
    public function reorderFormGroup($group, array $keys)
1463
    {
1464
        $formGroups = $this->getFormGroups();
1465
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1466
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups() on line 1464 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...
1467
    }
1468
1469
    /**
1470
     * {@inheritdoc}
1471
     */
1472
    public function getFormTabs()
1473
    {
1474
        return $this->formTabs;
1475
    }
1476
1477
    /**
1478
     * {@inheritdoc}
1479
     */
1480
    public function setFormTabs(array $formTabs)
1481
    {
1482
        $this->formTabs = $formTabs;
1483
    }
1484
1485
    /**
1486
     * {@inheritdoc}
1487
     */
1488
    public function getShowTabs()
1489
    {
1490
        return $this->showTabs;
1491
    }
1492
1493
    /**
1494
     * {@inheritdoc}
1495
     */
1496
    public function setShowTabs(array $showTabs)
1497
    {
1498
        $this->showTabs = $showTabs;
1499
    }
1500
1501
    /**
1502
     * {@inheritdoc}
1503
     */
1504
    public function getShowGroups()
1505
    {
1506
        return $this->showGroups;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->showGroups; of type array|boolean adds the type boolean to the return on line 1506 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getShowGroups of type array.
Loading history...
1507
    }
1508
1509
    /**
1510
     * {@inheritdoc}
1511
     */
1512
    public function setShowGroups(array $showGroups)
1513
    {
1514
        $this->showGroups = $showGroups;
1515
    }
1516
1517
    /**
1518
     * {@inheritdoc}
1519
     */
1520
    public function reorderShowGroup($group, array $keys)
1521
    {
1522
        $showGroups = $this->getShowGroups();
1523
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1524
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups() on line 1522 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...
1525
    }
1526
1527
    /**
1528
     * {@inheritdoc}
1529
     */
1530
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
1531
    {
1532
        $this->parentFieldDescription = $parentFieldDescription;
1533
    }
1534
1535
    /**
1536
     * {@inheritdoc}
1537
     */
1538
    public function getParentFieldDescription()
1539
    {
1540
        return $this->parentFieldDescription;
1541
    }
1542
1543
    /**
1544
     * {@inheritdoc}
1545
     */
1546
    public function hasParentFieldDescription()
1547
    {
1548
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1549
    }
1550
1551
    /**
1552
     * {@inheritdoc}
1553
     */
1554
    public function setSubject($subject)
1555
    {
1556
        if (is_object($subject) && !is_a($subject, $this->class, true)) {
1557
            $message = <<<'EOT'
1558
You are trying to set entity an instance of "%s",
1559
which is not the one registered with this admin class ("%s").
1560
This is deprecated since 3.5 and will no longer be supported in 4.0.
1561
EOT;
1562
1563
            @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...
1564
                sprintf($message, get_class($subject), $this->class),
1565
                E_USER_DEPRECATED
1566
            ); // NEXT_MAJOR : throw an exception instead
1567
        }
1568
1569
        $this->subject = $subject;
1570
    }
1571
1572
    /**
1573
     * {@inheritdoc}
1574
     */
1575
    public function getSubject()
1576
    {
1577
        if (null === $this->subject && $this->request && !$this->hasParentFieldDescription()) {
1578
            $id = $this->request->get($this->getIdParameter());
1579
1580
            if (null !== $id) {
1581
                $this->subject = $this->getModelManager()->find($this->class, $id);
1582
            }
1583
        }
1584
1585
        return $this->subject;
1586
    }
1587
1588
    /**
1589
     * {@inheritdoc}
1590
     */
1591
    public function hasSubject()
1592
    {
1593
        return (bool) $this->getSubject();
1594
    }
1595
1596
    /**
1597
     * {@inheritdoc}
1598
     */
1599
    public function getFormFieldDescriptions()
1600
    {
1601
        $this->buildForm();
1602
1603
        return $this->formFieldDescriptions;
1604
    }
1605
1606
    /**
1607
     * {@inheritdoc}
1608
     */
1609
    public function getFormFieldDescription($name)
1610
    {
1611
        return $this->hasFormFieldDescription($name) ? $this->formFieldDescriptions[$name] : null;
1612
    }
1613
1614
    /**
1615
     * Returns true if the admin has a FieldDescription with the given $name.
1616
     *
1617
     * @param string $name
1618
     *
1619
     * @return bool
1620
     */
1621
    public function hasFormFieldDescription($name)
1622
    {
1623
        return array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1624
    }
1625
1626
    /**
1627
     * {@inheritdoc}
1628
     */
1629
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1630
    {
1631
        $this->formFieldDescriptions[$name] = $fieldDescription;
1632
    }
1633
1634
    /**
1635
     * remove a FieldDescription.
1636
     *
1637
     * @param string $name
1638
     */
1639
    public function removeFormFieldDescription($name)
1640
    {
1641
        unset($this->formFieldDescriptions[$name]);
1642
    }
1643
1644
    /**
1645
     * build and return the collection of form FieldDescription.
1646
     *
1647
     * @return array collection of form FieldDescription
1648
     */
1649
    public function getShowFieldDescriptions()
1650
    {
1651
        $this->buildShow();
1652
1653
        return $this->showFieldDescriptions;
1654
    }
1655
1656
    /**
1657
     * Returns the form FieldDescription with the given $name.
1658
     *
1659
     * @param string $name
1660
     *
1661
     * @return FieldDescriptionInterface
1662
     */
1663
    public function getShowFieldDescription($name)
1664
    {
1665
        $this->buildShow();
1666
1667
        return $this->hasShowFieldDescription($name) ? $this->showFieldDescriptions[$name] : null;
1668
    }
1669
1670
    /**
1671
     * {@inheritdoc}
1672
     */
1673
    public function hasShowFieldDescription($name)
1674
    {
1675
        return array_key_exists($name, $this->showFieldDescriptions);
1676
    }
1677
1678
    /**
1679
     * {@inheritdoc}
1680
     */
1681
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1682
    {
1683
        $this->showFieldDescriptions[$name] = $fieldDescription;
1684
    }
1685
1686
    /**
1687
     * {@inheritdoc}
1688
     */
1689
    public function removeShowFieldDescription($name)
1690
    {
1691
        unset($this->showFieldDescriptions[$name]);
1692
    }
1693
1694
    /**
1695
     * {@inheritdoc}
1696
     */
1697
    public function getListFieldDescriptions()
1698
    {
1699
        $this->buildList();
1700
1701
        return $this->listFieldDescriptions;
1702
    }
1703
1704
    /**
1705
     * {@inheritdoc}
1706
     */
1707
    public function getListFieldDescription($name)
1708
    {
1709
        return $this->hasListFieldDescription($name) ? $this->listFieldDescriptions[$name] : null;
1710
    }
1711
1712
    /**
1713
     * {@inheritdoc}
1714
     */
1715
    public function hasListFieldDescription($name)
1716
    {
1717
        $this->buildList();
1718
1719
        return array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1720
    }
1721
1722
    /**
1723
     * {@inheritdoc}
1724
     */
1725
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1726
    {
1727
        $this->listFieldDescriptions[$name] = $fieldDescription;
1728
    }
1729
1730
    /**
1731
     * {@inheritdoc}
1732
     */
1733
    public function removeListFieldDescription($name)
1734
    {
1735
        unset($this->listFieldDescriptions[$name]);
1736
    }
1737
1738
    /**
1739
     * {@inheritdoc}
1740
     */
1741
    public function getFilterFieldDescription($name)
1742
    {
1743
        return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null;
1744
    }
1745
1746
    /**
1747
     * {@inheritdoc}
1748
     */
1749
    public function hasFilterFieldDescription($name)
1750
    {
1751
        return array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1752
    }
1753
1754
    /**
1755
     * {@inheritdoc}
1756
     */
1757
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1758
    {
1759
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1760
    }
1761
1762
    /**
1763
     * {@inheritdoc}
1764
     */
1765
    public function removeFilterFieldDescription($name)
1766
    {
1767
        unset($this->filterFieldDescriptions[$name]);
1768
    }
1769
1770
    /**
1771
     * {@inheritdoc}
1772
     */
1773
    public function getFilterFieldDescriptions()
1774
    {
1775
        $this->buildDatagrid();
1776
1777
        return $this->filterFieldDescriptions;
1778
    }
1779
1780
    /**
1781
     * {@inheritdoc}
1782
     */
1783
    public function addChild(AdminInterface $child)
1784
    {
1785
        for ($parentAdmin = $this; null !== $parentAdmin; $parentAdmin = $parentAdmin->getParent()) {
1786
            if ($parentAdmin->getCode() !== $child->getCode()) {
1787
                continue;
1788
            }
1789
1790
            throw new \RuntimeException(sprintf(
1791
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1792
                $child->getCode(), $this->getCode()
1793
            ));
1794
        }
1795
1796
        $this->children[$child->getCode()] = $child;
1797
1798
        $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...
1799
    }
1800
1801
    /**
1802
     * {@inheritdoc}
1803
     */
1804
    public function hasChild($code)
1805
    {
1806
        return isset($this->children[$code]);
1807
    }
1808
1809
    /**
1810
     * {@inheritdoc}
1811
     */
1812
    public function getChildren()
1813
    {
1814
        return $this->children;
1815
    }
1816
1817
    /**
1818
     * {@inheritdoc}
1819
     */
1820
    public function getChild($code)
1821
    {
1822
        return $this->hasChild($code) ? $this->children[$code] : null;
1823
    }
1824
1825
    /**
1826
     * {@inheritdoc}
1827
     */
1828
    public function setParent(AdminInterface $parent)
1829
    {
1830
        $this->parent = $parent;
1831
    }
1832
1833
    /**
1834
     * {@inheritdoc}
1835
     */
1836
    public function getParent()
1837
    {
1838
        return $this->parent;
1839
    }
1840
1841
    /**
1842
     * {@inheritdoc}
1843
     */
1844
    final public function getRootAncestor()
1845
    {
1846
        $parent = $this;
1847
1848
        while ($parent->isChild()) {
1849
            $parent = $parent->getParent();
1850
        }
1851
1852
        return $parent;
1853
    }
1854
1855
    /**
1856
     * {@inheritdoc}
1857
     */
1858
    final public function getChildDepth()
1859
    {
1860
        $parent = $this;
1861
        $depth = 0;
1862
1863
        while ($parent->isChild()) {
1864
            $parent = $parent->getParent();
1865
            ++$depth;
1866
        }
1867
1868
        return $depth;
1869
    }
1870
1871
    /**
1872
     * {@inheritdoc}
1873
     */
1874
    final public function getCurrentLeafChildAdmin()
1875
    {
1876
        $child = $this->getCurrentChildAdmin();
1877
1878
        if (null === $child) {
1879
            return;
1880
        }
1881
1882
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1883
            $child = $c;
1884
        }
1885
1886
        return $child;
1887
    }
1888
1889
    /**
1890
     * {@inheritdoc}
1891
     */
1892
    public function isChild()
1893
    {
1894
        return $this->parent instanceof AdminInterface;
1895
    }
1896
1897
    /**
1898
     * Returns true if the admin has children, false otherwise.
1899
     *
1900
     * @return bool if the admin has children
1901
     */
1902
    public function hasChildren()
1903
    {
1904
        return count($this->children) > 0;
1905
    }
1906
1907
    /**
1908
     * {@inheritdoc}
1909
     */
1910
    public function setUniqid($uniqid)
1911
    {
1912
        $this->uniqid = $uniqid;
1913
    }
1914
1915
    /**
1916
     * {@inheritdoc}
1917
     */
1918
    public function getUniqid()
1919
    {
1920
        if (!$this->uniqid) {
1921
            $this->uniqid = 's'.uniqid();
1922
        }
1923
1924
        return $this->uniqid;
1925
    }
1926
1927
    /**
1928
     * {@inheritdoc}
1929
     */
1930
    public function getClassnameLabel()
1931
    {
1932
        return $this->classnameLabel;
1933
    }
1934
1935
    /**
1936
     * {@inheritdoc}
1937
     */
1938
    public function getPersistentParameters()
1939
    {
1940
        $parameters = [];
1941
1942
        foreach ($this->getExtensions() as $extension) {
1943
            $params = $extension->getPersistentParameters($this);
1944
1945
            if (!is_array($params)) {
1946
                throw new \RuntimeException(sprintf('The %s::getPersistentParameters must return an array', get_class($extension)));
1947
            }
1948
1949
            $parameters = array_merge($parameters, $params);
1950
        }
1951
1952
        return $parameters;
1953
    }
1954
1955
    /**
1956
     * {@inheritdoc}
1957
     */
1958
    public function getPersistentParameter($name)
1959
    {
1960
        $parameters = $this->getPersistentParameters();
1961
1962
        return isset($parameters[$name]) ? $parameters[$name] : null;
1963
    }
1964
1965
    /**
1966
     * {@inheritdoc}
1967
     */
1968
    public function setCurrentChild($currentChild)
1969
    {
1970
        $this->currentChild = $currentChild;
1971
    }
1972
1973
    /**
1974
     * {@inheritdoc}
1975
     */
1976
    public function getCurrentChild()
1977
    {
1978
        return $this->currentChild;
1979
    }
1980
1981
    /**
1982
     * Returns the current child admin instance.
1983
     *
1984
     * @return AdminInterface|null the current child admin instance
1985
     */
1986
    public function getCurrentChildAdmin()
1987
    {
1988
        foreach ($this->children as $children) {
1989
            if ($children->getCurrentChild()) {
1990
                return $children;
1991
            }
1992
        }
1993
1994
        return;
1995
    }
1996
1997
    /**
1998
     * {@inheritdoc}
1999
     */
2000
    public function trans($id, array $parameters = [], $domain = null, $locale = null)
2001
    {
2002
        @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...
2003
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2004
            E_USER_DEPRECATED
2005
        );
2006
2007
        $domain = $domain ?: $this->getTranslationDomain();
2008
2009
        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 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...
2010
    }
2011
2012
    /**
2013
     * Translate a message id.
2014
     *
2015
     * NEXT_MAJOR: remove this method
2016
     *
2017
     * @param string      $id
2018
     * @param int         $count
2019
     * @param array       $parameters
2020
     * @param string|null $domain
2021
     * @param string|null $locale
2022
     *
2023
     * @return string the translated string
2024
     *
2025
     * @deprecated since 3.9, to be removed with 4.0
2026
     */
2027
    public function transChoice($id, $count, array $parameters = [], $domain = null, $locale = null)
2028
    {
2029
        @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...
2030
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2031
            E_USER_DEPRECATED
2032
        );
2033
2034
        $domain = $domain ?: $this->getTranslationDomain();
2035
2036
        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 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...
2037
    }
2038
2039
    /**
2040
     * {@inheritdoc}
2041
     */
2042
    public function setTranslationDomain($translationDomain)
2043
    {
2044
        $this->translationDomain = $translationDomain;
2045
    }
2046
2047
    /**
2048
     * {@inheritdoc}
2049
     */
2050
    public function getTranslationDomain()
2051
    {
2052
        return $this->translationDomain;
2053
    }
2054
2055
    /**
2056
     * {@inheritdoc}
2057
     *
2058
     * NEXT_MAJOR: remove this method
2059
     *
2060
     * @deprecated since 3.9, to be removed with 4.0
2061
     */
2062
    public function setTranslator(TranslatorInterface $translator)
2063
    {
2064
        $args = func_get_args();
2065
        if (isset($args[1]) && $args[1]) {
2066
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2067
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2068
                E_USER_DEPRECATED
2069
            );
2070
        }
2071
2072
        $this->translator = $translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since 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...
2073
    }
2074
2075
    /**
2076
     * {@inheritdoc}
2077
     *
2078
     * NEXT_MAJOR: remove this method
2079
     *
2080
     * @deprecated since 3.9, to be removed with 4.0
2081
     */
2082
    public function getTranslator()
2083
    {
2084
        @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...
2085
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2086
            E_USER_DEPRECATED
2087
        );
2088
2089
        return $this->translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since 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...
2090
    }
2091
2092
    /**
2093
     * {@inheritdoc}
2094
     */
2095
    public function getTranslationLabel($label, $context = '', $type = '')
2096
    {
2097
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2098
    }
2099
2100
    /**
2101
     * {@inheritdoc}
2102
     */
2103
    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...
2104
    {
2105
        $this->request = $request;
2106
2107
        foreach ($this->getChildren() as $children) {
2108
            $children->setRequest($request);
2109
        }
2110
    }
2111
2112
    /**
2113
     * {@inheritdoc}
2114
     */
2115
    public function getRequest()
2116
    {
2117
        if (!$this->request) {
2118
            throw new \RuntimeException('The Request object has not been set');
2119
        }
2120
2121
        return $this->request;
2122
    }
2123
2124
    /**
2125
     * {@inheritdoc}
2126
     */
2127
    public function hasRequest()
2128
    {
2129
        return null !== $this->request;
2130
    }
2131
2132
    /**
2133
     * {@inheritdoc}
2134
     */
2135
    public function setFormContractor(FormContractorInterface $formBuilder)
2136
    {
2137
        $this->formContractor = $formBuilder;
2138
    }
2139
2140
    /**
2141
     * @return FormContractorInterface
2142
     */
2143
    public function getFormContractor()
2144
    {
2145
        return $this->formContractor;
2146
    }
2147
2148
    /**
2149
     * {@inheritdoc}
2150
     */
2151
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder)
2152
    {
2153
        $this->datagridBuilder = $datagridBuilder;
2154
    }
2155
2156
    /**
2157
     * {@inheritdoc}
2158
     */
2159
    public function getDatagridBuilder()
2160
    {
2161
        return $this->datagridBuilder;
2162
    }
2163
2164
    /**
2165
     * {@inheritdoc}
2166
     */
2167
    public function setListBuilder(ListBuilderInterface $listBuilder)
2168
    {
2169
        $this->listBuilder = $listBuilder;
2170
    }
2171
2172
    /**
2173
     * {@inheritdoc}
2174
     */
2175
    public function getListBuilder()
2176
    {
2177
        return $this->listBuilder;
2178
    }
2179
2180
    /**
2181
     * @param ShowBuilderInterface $showBuilder
2182
     */
2183
    public function setShowBuilder(ShowBuilderInterface $showBuilder)
2184
    {
2185
        $this->showBuilder = $showBuilder;
2186
    }
2187
2188
    /**
2189
     * @return ShowBuilderInterface
2190
     */
2191
    public function getShowBuilder()
2192
    {
2193
        return $this->showBuilder;
2194
    }
2195
2196
    /**
2197
     * {@inheritdoc}
2198
     */
2199
    public function setConfigurationPool(Pool $configurationPool)
2200
    {
2201
        $this->configurationPool = $configurationPool;
2202
    }
2203
2204
    /**
2205
     * @return Pool
2206
     */
2207
    public function getConfigurationPool()
2208
    {
2209
        return $this->configurationPool;
2210
    }
2211
2212
    /**
2213
     * {@inheritdoc}
2214
     */
2215
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator)
2216
    {
2217
        $this->routeGenerator = $routeGenerator;
2218
    }
2219
2220
    /**
2221
     * @return RouteGeneratorInterface
2222
     */
2223
    public function getRouteGenerator()
2224
    {
2225
        return $this->routeGenerator;
2226
    }
2227
2228
    /**
2229
     * {@inheritdoc}
2230
     */
2231
    public function getCode()
2232
    {
2233
        return $this->code;
2234
    }
2235
2236
    /**
2237
     * {@inheritdoc}
2238
     */
2239
    public function getBaseCodeRoute()
2240
    {
2241
        if ($this->isChild()) {
2242
            return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2243
        }
2244
2245
        return $this->getCode();
2246
    }
2247
2248
    /**
2249
     * {@inheritdoc}
2250
     */
2251
    public function getModelManager()
2252
    {
2253
        return $this->modelManager;
2254
    }
2255
2256
    /**
2257
     * @param ModelManagerInterface $modelManager
2258
     */
2259
    public function setModelManager(ModelManagerInterface $modelManager)
2260
    {
2261
        $this->modelManager = $modelManager;
2262
    }
2263
2264
    /**
2265
     * {@inheritdoc}
2266
     */
2267
    public function getManagerType()
2268
    {
2269
        return $this->managerType;
2270
    }
2271
2272
    /**
2273
     * @param string $type
2274
     */
2275
    public function setManagerType($type)
2276
    {
2277
        $this->managerType = $type;
2278
    }
2279
2280
    /**
2281
     * {@inheritdoc}
2282
     */
2283
    public function getObjectIdentifier()
2284
    {
2285
        return $this->getCode();
2286
    }
2287
2288
    /**
2289
     * Set the roles and permissions per role.
2290
     *
2291
     * @param array $information
2292
     */
2293
    public function setSecurityInformation(array $information)
2294
    {
2295
        $this->securityInformation = $information;
2296
    }
2297
2298
    /**
2299
     * {@inheritdoc}
2300
     */
2301
    public function getSecurityInformation()
2302
    {
2303
        return $this->securityInformation;
2304
    }
2305
2306
    /**
2307
     * Return the list of permissions the user should have in order to display the admin.
2308
     *
2309
     * @param string $context
2310
     *
2311
     * @return array
2312
     */
2313
    public function getPermissionsShow($context)
2314
    {
2315
        switch ($context) {
2316
            case self::CONTEXT_DASHBOARD:
2317
            case self::CONTEXT_MENU:
2318
            default:
2319
                return ['LIST'];
2320
        }
2321
    }
2322
2323
    /**
2324
     * {@inheritdoc}
2325
     */
2326
    public function showIn($context)
2327
    {
2328
        switch ($context) {
2329
            case self::CONTEXT_DASHBOARD:
2330
            case self::CONTEXT_MENU:
2331
            default:
2332
                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...
2333
        }
2334
    }
2335
2336
    /**
2337
     * {@inheritdoc}
2338
     */
2339
    public function createObjectSecurity($object)
2340
    {
2341
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2342
    }
2343
2344
    /**
2345
     * {@inheritdoc}
2346
     */
2347
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
2348
    {
2349
        $this->securityHandler = $securityHandler;
2350
    }
2351
2352
    /**
2353
     * {@inheritdoc}
2354
     */
2355
    public function getSecurityHandler()
2356
    {
2357
        return $this->securityHandler;
2358
    }
2359
2360
    /**
2361
     * {@inheritdoc}
2362
     */
2363
    public function isGranted($name, $object = null)
2364
    {
2365
        $key = md5(json_encode($name).($object ? '/'.spl_object_hash($object) : ''));
2366
2367
        if (!array_key_exists($key, $this->cacheIsGranted)) {
2368
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2369
        }
2370
2371
        return $this->cacheIsGranted[$key];
2372
    }
2373
2374
    /**
2375
     * {@inheritdoc}
2376
     */
2377
    public function getUrlsafeIdentifier($entity)
2378
    {
2379
        return $this->getModelManager()->getUrlsafeIdentifier($entity);
2380
    }
2381
2382
    /**
2383
     * {@inheritdoc}
2384
     */
2385
    public function getNormalizedIdentifier($entity)
2386
    {
2387
        return $this->getModelManager()->getNormalizedIdentifier($entity);
2388
    }
2389
2390
    /**
2391
     * {@inheritdoc}
2392
     */
2393
    public function id($entity)
2394
    {
2395
        return $this->getNormalizedIdentifier($entity);
2396
    }
2397
2398
    /**
2399
     * {@inheritdoc}
2400
     */
2401
    public function setValidator($validator)
2402
    {
2403
        // TODO: Remove it when bumping requirements to SF 2.5+
2404
        if (!$validator instanceof ValidatorInterface && !$validator instanceof LegacyValidatorInterface) {
0 ignored issues
show
Bug introduced by
The class Symfony\Component\Validator\ValidatorInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

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

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

2. Missing use statement

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

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

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

Loading history...
2405
            throw new \InvalidArgumentException(
2406
                'Argument 1 must be an instance of Symfony\Component\Validator\Validator\ValidatorInterface'
2407
                .' or Symfony\Component\Validator\ValidatorInterface'
2408
            );
2409
        }
2410
2411
        $this->validator = $validator;
2412
    }
2413
2414
    /**
2415
     * {@inheritdoc}
2416
     */
2417
    public function getValidator()
2418
    {
2419
        return $this->validator;
2420
    }
2421
2422
    /**
2423
     * {@inheritdoc}
2424
     */
2425
    public function getShow()
2426
    {
2427
        $this->buildShow();
2428
2429
        return $this->show;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->show; (Sonata\AdminBundle\Admin...ldDescriptionCollection) is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin\AdminInterface::getShow of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
2430
    }
2431
2432
    /**
2433
     * {@inheritdoc}
2434
     */
2435
    public function setFormTheme(array $formTheme)
2436
    {
2437
        $this->formTheme = $formTheme;
2438
    }
2439
2440
    /**
2441
     * {@inheritdoc}
2442
     */
2443
    public function getFormTheme()
2444
    {
2445
        return $this->formTheme;
2446
    }
2447
2448
    /**
2449
     * {@inheritdoc}
2450
     */
2451
    public function setFilterTheme(array $filterTheme)
2452
    {
2453
        $this->filterTheme = $filterTheme;
2454
    }
2455
2456
    /**
2457
     * {@inheritdoc}
2458
     */
2459
    public function getFilterTheme()
2460
    {
2461
        return $this->filterTheme;
2462
    }
2463
2464
    /**
2465
     * {@inheritdoc}
2466
     */
2467
    public function addExtension(AdminExtensionInterface $extension)
2468
    {
2469
        $this->extensions[] = $extension;
2470
    }
2471
2472
    /**
2473
     * {@inheritdoc}
2474
     */
2475
    public function getExtensions()
2476
    {
2477
        return $this->extensions;
2478
    }
2479
2480
    /**
2481
     * {@inheritdoc}
2482
     */
2483
    public function setMenuFactory(MenuFactoryInterface $menuFactory)
2484
    {
2485
        $this->menuFactory = $menuFactory;
2486
    }
2487
2488
    /**
2489
     * {@inheritdoc}
2490
     */
2491
    public function getMenuFactory()
2492
    {
2493
        return $this->menuFactory;
2494
    }
2495
2496
    /**
2497
     * {@inheritdoc}
2498
     */
2499
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder)
2500
    {
2501
        $this->routeBuilder = $routeBuilder;
2502
    }
2503
2504
    /**
2505
     * {@inheritdoc}
2506
     */
2507
    public function getRouteBuilder()
2508
    {
2509
        return $this->routeBuilder;
2510
    }
2511
2512
    /**
2513
     * {@inheritdoc}
2514
     */
2515
    public function toString($object)
2516
    {
2517
        if (!is_object($object)) {
2518
            return '';
2519
        }
2520
2521
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2522
            return (string) $object;
2523
        }
2524
2525
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2526
    }
2527
2528
    /**
2529
     * {@inheritdoc}
2530
     */
2531
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy)
2532
    {
2533
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2534
    }
2535
2536
    /**
2537
     * {@inheritdoc}
2538
     */
2539
    public function getLabelTranslatorStrategy()
2540
    {
2541
        return $this->labelTranslatorStrategy;
2542
    }
2543
2544
    /**
2545
     * {@inheritdoc}
2546
     */
2547
    public function supportsPreviewMode()
2548
    {
2549
        return $this->supportsPreviewMode;
2550
    }
2551
2552
    /**
2553
     * Set custom per page options.
2554
     *
2555
     * @param array $options
2556
     */
2557
    public function setPerPageOptions(array $options)
2558
    {
2559
        $this->perPageOptions = $options;
2560
    }
2561
2562
    /**
2563
     * Returns predefined per page options.
2564
     *
2565
     * @return array
2566
     */
2567
    public function getPerPageOptions()
2568
    {
2569
        return $this->perPageOptions;
2570
    }
2571
2572
    /**
2573
     * Set pager type.
2574
     *
2575
     * @param string $pagerType
2576
     */
2577
    public function setPagerType($pagerType)
2578
    {
2579
        $this->pagerType = $pagerType;
2580
    }
2581
2582
    /**
2583
     * Get pager type.
2584
     *
2585
     * @return string
2586
     */
2587
    public function getPagerType()
2588
    {
2589
        return $this->pagerType;
2590
    }
2591
2592
    /**
2593
     * Returns true if the per page value is allowed, false otherwise.
2594
     *
2595
     * @param int $perPage
2596
     *
2597
     * @return bool
2598
     */
2599
    public function determinedPerPageValue($perPage)
2600
    {
2601
        return in_array($perPage, $this->perPageOptions);
2602
    }
2603
2604
    /**
2605
     * {@inheritdoc}
2606
     */
2607
    public function isAclEnabled()
2608
    {
2609
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2610
    }
2611
2612
    /**
2613
     * {@inheritdoc}
2614
     */
2615
    public function getObjectMetadata($object)
2616
    {
2617
        return new Metadata($this->toString($object));
2618
    }
2619
2620
    /**
2621
     * {@inheritdoc}
2622
     */
2623
    public function getListModes()
2624
    {
2625
        return $this->listModes;
2626
    }
2627
2628
    /**
2629
     * {@inheritdoc}
2630
     */
2631
    public function setListMode($mode)
2632
    {
2633
        if (!$this->hasRequest()) {
2634
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2635
        }
2636
2637
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2638
    }
2639
2640
    /**
2641
     * {@inheritdoc}
2642
     */
2643
    public function getListMode()
2644
    {
2645
        if (!$this->hasRequest()) {
2646
            return 'list';
2647
        }
2648
2649
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2650
    }
2651
2652
    /**
2653
     * {@inheritdoc}
2654
     */
2655
    public function getAccessMapping()
2656
    {
2657
        return $this->accessMapping;
2658
    }
2659
2660
    /**
2661
     * {@inheritdoc}
2662
     */
2663
    public function checkAccess($action, $object = null)
2664
    {
2665
        $access = $this->getAccess();
2666
2667
        if (!array_key_exists($action, $access)) {
2668
            throw new \InvalidArgumentException(sprintf(
2669
                'Action "%s" could not be found in access mapping.'
2670
                .' Please make sure your action is defined into your admin class accessMapping property.',
2671
                $action
2672
            ));
2673
        }
2674
2675
        if (!is_array($access[$action])) {
2676
            $access[$action] = [$access[$action]];
2677
        }
2678
2679
        foreach ($access[$action] as $role) {
2680
            if (false === $this->isGranted($role, $object)) {
2681
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2682
            }
2683
        }
2684
    }
2685
2686
    /**
2687
     * {@inheritdoc}
2688
     */
2689
    public function hasAccess($action, $object = null)
2690
    {
2691
        $access = $this->getAccess();
2692
2693
        if (!array_key_exists($action, $access)) {
2694
            return false;
2695
        }
2696
2697
        if (!is_array($access[$action])) {
2698
            $access[$action] = [$access[$action]];
2699
        }
2700
2701
        foreach ($access[$action] as $role) {
2702
            if (false === $this->isGranted($role, $object)) {
2703
                return false;
2704
            }
2705
        }
2706
2707
        return true;
2708
    }
2709
2710
    /**
2711
     * {@inheritdoc}
2712
     */
2713
    final public function getActionButtons($action, $object = null)
2714
    {
2715
        $buttonList = [];
2716
2717
        if (in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'])
2718
            && $this->hasAccess('create')
2719
            && $this->hasRoute('create')
2720
        ) {
2721
            $buttonList['create'] = [
2722
                'template' => $this->getTemplate('button_create'),
2723
            ];
2724
        }
2725
2726
        if (in_array($action, ['show', 'delete', 'acl', 'history'])
2727
            && $this->canAccessObject('edit', $object)
2728
            && $this->hasRoute('edit')
2729
        ) {
2730
            $buttonList['edit'] = [
2731
                'template' => $this->getTemplate('button_edit'),
2732
            ];
2733
        }
2734
2735
        if (in_array($action, ['show', 'edit', 'acl'])
2736
            && $this->canAccessObject('history', $object)
2737
            && $this->hasRoute('history')
2738
        ) {
2739
            $buttonList['history'] = [
2740
                'template' => $this->getTemplate('button_history'),
2741
            ];
2742
        }
2743
2744
        if (in_array($action, ['edit', 'history'])
2745
            && $this->isAclEnabled()
2746
            && $this->canAccessObject('acl', $object)
2747
            && $this->hasRoute('acl')
2748
        ) {
2749
            $buttonList['acl'] = [
2750
                'template' => $this->getTemplate('button_acl'),
2751
            ];
2752
        }
2753
2754
        if (in_array($action, ['edit', 'history', 'acl'])
2755
            && $this->canAccessObject('show', $object)
2756
            && count($this->getShow()) > 0
2757
            && $this->hasRoute('show')
2758
        ) {
2759
            $buttonList['show'] = [
2760
                'template' => $this->getTemplate('button_show'),
2761
            ];
2762
        }
2763
2764
        if (in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'])
2765
            && $this->hasAccess('list')
2766
            && $this->hasRoute('list')
2767
        ) {
2768
            $buttonList['list'] = [
2769
                'template' => $this->getTemplate('button_list'),
2770
            ];
2771
        }
2772
2773
        $buttonList = $this->configureActionButtons($buttonList, $action, $object);
2774
2775
        foreach ($this->getExtensions() as $extension) {
2776
            $buttonList = $extension->configureActionButtons($this, $buttonList, $action, $object);
2777
        }
2778
2779
        return $buttonList;
2780
    }
2781
2782
    /**
2783
     * {@inheritdoc}
2784
     */
2785
    public function getDashboardActions()
2786
    {
2787
        $actions = [];
2788
2789
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2790
            $actions['create'] = [
2791
                'label' => 'link_add',
2792
                'translation_domain' => 'SonataAdminBundle',
2793
                'template' => $this->getTemplate('action_create'),
2794
                'url' => $this->generateUrl('create'),
2795
                'icon' => 'plus-circle',
2796
            ];
2797
        }
2798
2799
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2800
            $actions['list'] = [
2801
                'label' => 'link_list',
2802
                'translation_domain' => 'SonataAdminBundle',
2803
                'url' => $this->generateUrl('list'),
2804
                'icon' => 'list',
2805
            ];
2806
        }
2807
2808
        return $actions;
2809
    }
2810
2811
    /**
2812
     * {@inheritdoc}
2813
     */
2814
    final public function showMosaicButton($isShown)
2815
    {
2816
        if ($isShown) {
2817
            $this->listModes['mosaic'] = ['class' => self::MOSAIC_ICON_CLASS];
2818
        } else {
2819
            unset($this->listModes['mosaic']);
2820
        }
2821
    }
2822
2823
    /**
2824
     * {@inheritdoc}
2825
     */
2826
    final public function getSearchResultLink($object)
2827
    {
2828
        foreach ($this->searchResultActions as $action) {
2829
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2830
                return $this->generateObjectUrl($action, $object);
2831
            }
2832
        }
2833
2834
        return;
2835
    }
2836
2837
    /**
2838
     * Checks if a filter type is set to a default value.
2839
     *
2840
     * @param string $name
2841
     *
2842
     * @return bool
2843
     */
2844
    final public function isDefaultFilter($name)
2845
    {
2846
        $filter = $this->getFilterParameters();
2847
        $default = $this->getDefaultFilterValues();
2848
2849
        if (!array_key_exists($name, $filter) || !array_key_exists($name, $default)) {
2850
            return false;
2851
        }
2852
2853
        return $filter[$name] == $default[$name];
2854
    }
2855
2856
    /**
2857
     * Check object existence and access, without throw Exception.
2858
     *
2859
     * @param string $action
2860
     * @param object $object
2861
     *
2862
     * @return bool
2863
     */
2864
    public function canAccessObject($action, $object)
2865
    {
2866
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2867
    }
2868
2869
    /**
2870
     * Hook to run after initilization.
2871
     */
2872
    protected function configure()
2873
    {
2874
    }
2875
2876
    /**
2877
     * urlize the given word.
2878
     *
2879
     * @param string $word
2880
     * @param string $sep  the separator
2881
     *
2882
     * @return string
2883
     */
2884
    final protected function urlize($word, $sep = '_')
2885
    {
2886
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
2887
    }
2888
2889
    /**
2890
     * Returns a list of default filters.
2891
     *
2892
     * @return array
2893
     */
2894
    final protected function getDefaultFilterValues()
2895
    {
2896
        $defaultFilterValues = [];
2897
2898
        $this->configureDefaultFilterValues($defaultFilterValues);
2899
2900
        foreach ($this->getExtensions() as $extension) {
2901
            // NEXT_MAJOR: remove method check in next major release
2902
            if (method_exists($extension, 'configureDefaultFilterValues')) {
2903
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2904
            }
2905
        }
2906
2907
        return $defaultFilterValues;
2908
    }
2909
2910
    /**
2911
     * {@inheritdoc}
2912
     */
2913
    protected function configureFormFields(FormMapper $form)
2914
    {
2915
    }
2916
2917
    /**
2918
     * @param ListMapper $list
2919
     */
2920
    protected function configureListFields(ListMapper $list)
2921
    {
2922
    }
2923
2924
    /**
2925
     * @param DatagridMapper $filter
2926
     */
2927
    protected function configureDatagridFilters(DatagridMapper $filter)
2928
    {
2929
    }
2930
2931
    /**
2932
     * @param ShowMapper $show
2933
     */
2934
    protected function configureShowFields(ShowMapper $show)
2935
    {
2936
    }
2937
2938
    /**
2939
     * @param RouteCollection $collection
2940
     */
2941
    protected function configureRoutes(RouteCollection $collection)
2942
    {
2943
    }
2944
2945
    /**
2946
     * Configure buttons for an action.
2947
     *
2948
     * @param array  $buttonList List of all action buttons
2949
     * @param string $action     Current action route
2950
     * @param object $object     Current object
2951
     *
2952
     * @return array
2953
     */
2954
    protected function configureActionButtons($buttonList, $action, $object = null)
0 ignored issues
show
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 $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...
2955
    {
2956
        return $buttonList;
2957
    }
2958
2959
    /**
2960
     * Allows you to customize batch actions.
2961
     *
2962
     * @param array $actions List of actions
2963
     *
2964
     * @return array
2965
     */
2966
    protected function configureBatchActions($actions)
2967
    {
2968
        return $actions;
2969
    }
2970
2971
    /**
2972
     * NEXT_MAJOR: remove this method.
2973
     *
2974
     * @param MenuItemInterface $menu
2975
     * @param                   $action
2976
     * @param AdminInterface    $childAdmin
2977
     *
2978
     * @return mixed
2979
     *
2980
     * @deprecated Use configureTabMenu instead
2981
     */
2982
    protected function configureSideMenu(MenuItemInterface $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...
2983
    {
2984
    }
2985
2986
    /**
2987
     * Configures the tab menu in your admin.
2988
     *
2989
     * @param MenuItemInterface $menu
2990
     * @param string            $action
2991
     * @param AdminInterface    $childAdmin
2992
     *
2993
     * @return mixed
2994
     */
2995
    protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
2996
    {
2997
        // Use configureSideMenu not to mess with previous overrides
2998
        // TODO remove once deprecation period is over
2999
        $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...
3000
    }
3001
3002
    /**
3003
     * build the view FieldDescription array.
3004
     */
3005
    protected function buildShow()
3006
    {
3007
        if ($this->show) {
3008
            return;
3009
        }
3010
3011
        $this->show = new FieldDescriptionCollection();
3012
        $mapper = new ShowMapper($this->showBuilder, $this->show, $this);
3013
3014
        $this->configureShowFields($mapper);
3015
3016
        foreach ($this->getExtensions() as $extension) {
3017
            $extension->configureShowFields($mapper);
3018
        }
3019
    }
3020
3021
    /**
3022
     * build the list FieldDescription array.
3023
     */
3024
    protected function buildList()
3025
    {
3026
        if ($this->list) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->list of type array 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...
3027
            return;
3028
        }
3029
3030
        $this->list = $this->getListBuilder()->getBaseList();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getListBuilder()->getBaseList() of type object<Sonata\AdminBundl...dDescriptionCollection> is incompatible with the declared type array of property $list.

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...
3031
3032
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
3033
3034
        if (count($this->getBatchActions()) > 0) {
3035
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3036
                $this->getClass(),
3037
                'batch',
3038
                [
3039
                    'label' => 'batch',
3040
                    'code' => '_batch',
3041
                    'sortable' => false,
3042
                    'virtual_field' => true,
3043
                ]
3044
            );
3045
3046
            $fieldDescription->setAdmin($this);
3047
            $fieldDescription->setTemplate($this->getTemplate('batch'));
3048
3049
            $mapper->add($fieldDescription, 'batch');
0 ignored issues
show
Documentation introduced by
$fieldDescription is of type object<Sonata\AdminBundl...ldDescriptionInterface>, 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...
3050
        }
3051
3052
        $this->configureListFields($mapper);
3053
3054
        foreach ($this->getExtensions() as $extension) {
3055
            $extension->configureListFields($mapper);
3056
        }
3057
3058
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
3059
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3060
                $this->getClass(),
3061
                'select',
3062
                [
3063
                    'label' => false,
3064
                    'code' => '_select',
3065
                    'sortable' => false,
3066
                    'virtual_field' => false,
3067
                ]
3068
            );
3069
3070
            $fieldDescription->setAdmin($this);
3071
            $fieldDescription->setTemplate($this->getTemplate('select'));
3072
3073
            $mapper->add($fieldDescription, 'select');
0 ignored issues
show
Documentation introduced by
$fieldDescription is of type object<Sonata\AdminBundl...ldDescriptionInterface>, 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...
3074
        }
3075
    }
3076
3077
    /**
3078
     * Build the form FieldDescription collection.
3079
     */
3080
    protected function buildForm()
3081
    {
3082
        if ($this->form) {
3083
            return;
3084
        }
3085
3086
        // append parent object if any
3087
        // todo : clean the way the Admin class can retrieve set the object
3088
        if ($this->isChild() && $this->getParentAssociationMapping()) {
3089
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
3090
3091
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3092
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3093
3094
            $object = $this->getSubject();
3095
3096
            $value = $propertyAccessor->getValue($object, $propertyPath);
3097
3098
            if (is_array($value) || ($value instanceof \Traversable && $value instanceof \ArrayAccess)) {
3099
                $value[] = $parent;
3100
                $propertyAccessor->setValue($object, $propertyPath, $value);
3101
            } else {
3102
                $propertyAccessor->setValue($object, $propertyPath, $parent);
3103
            }
3104
        }
3105
3106
        $this->form = $this->getFormBuilder()->getForm();
3107
    }
3108
3109
    /**
3110
     * Gets the subclass corresponding to the given name.
3111
     *
3112
     * @param string $name The name of the sub class
3113
     *
3114
     * @return string the subclass
3115
     */
3116
    protected function getSubClass($name)
3117
    {
3118
        if ($this->hasSubClass($name)) {
3119
            return $this->subClasses[$name];
3120
        }
3121
3122
        throw new \RuntimeException(sprintf(
3123
            'Unable to find the subclass `%s` for admin `%s`',
3124
            $name,
3125
            get_class($this)
3126
        ));
3127
    }
3128
3129
    /**
3130
     * Attach the inline validator to the model metadata, this must be done once per admin.
3131
     */
3132
    protected function attachInlineValidator()
3133
    {
3134
        $admin = $this;
3135
3136
        // add the custom inline validation option
3137
        // TODO: Remove conditional method when bumping requirements to SF 2.5+
3138
        if (method_exists($this->validator, 'getMetadataFor')) {
3139
            $metadata = $this->validator->getMetadataFor($this->getClass());
3140
        } else {
3141
            $metadata = $this->validator->getMetadataFactory()->getMetadataFor($this->getClass());
0 ignored issues
show
Bug introduced by
The method getMetadataFactory() does not seem to exist on object<Symfony\Component...tor\ValidatorInterface>.

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

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

Loading history...
3142
        }
3143
3144
        $metadata->addConstraint(new InlineConstraint([
3145
            'service' => $this,
3146
            'method' => function (ErrorElement $errorElement, $object) use ($admin) {
3147
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
3148
3149
                // This avoid the main validation to be cascaded to children
3150
                // The problem occurs when a model Page has a collection of Page as property
3151
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
3152
                    return;
3153
                }
3154
3155
                $admin->validate($errorElement, $object);
3156
3157
                foreach ($admin->getExtensions() as $extension) {
3158
                    $extension->validate($admin, $errorElement, $object);
3159
                }
3160
            },
3161
            'serializingWarning' => true,
3162
        ]));
3163
    }
3164
3165
    /**
3166
     * Predefine per page options.
3167
     */
3168
    protected function predefinePerPageOptions()
3169
    {
3170
        array_unshift($this->perPageOptions, $this->maxPerPage);
3171
        $this->perPageOptions = array_unique($this->perPageOptions);
3172
        sort($this->perPageOptions);
3173
    }
3174
3175
    /**
3176
     * Return list routes with permissions name.
3177
     *
3178
     * @return array
3179
     */
3180
    protected function getAccess()
3181
    {
3182
        $access = array_merge([
3183
            'acl' => 'MASTER',
3184
            'export' => 'EXPORT',
3185
            'historyCompareRevisions' => 'EDIT',
3186
            'historyViewRevision' => 'EDIT',
3187
            'history' => 'EDIT',
3188
            'edit' => 'EDIT',
3189
            'show' => 'VIEW',
3190
            'create' => 'CREATE',
3191
            'delete' => 'DELETE',
3192
            'batchDelete' => 'DELETE',
3193
            'list' => 'LIST',
3194
        ], $this->getAccessMapping());
3195
3196
        foreach ($this->extensions as $extension) {
3197
            $access = array_merge($access, $extension->getAccessMapping($this));
3198
        }
3199
3200
        return $access;
3201
    }
3202
3203
    /**
3204
     * Returns a list of default filters.
3205
     *
3206
     * @param array $filterValues
3207
     */
3208
    protected function configureDefaultFilterValues(array &$filterValues)
3209
    {
3210
    }
3211
3212
    /**
3213
     * {@inheritdoc}
3214
     */
3215
    private function buildDatagrid()
3216
    {
3217
        if ($this->datagrid) {
3218
            return;
3219
        }
3220
3221
        $filterParameters = $this->getFilterParameters();
3222
3223
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
3224
        if (isset($filterParameters['_sort_by']) && is_string($filterParameters['_sort_by'])) {
3225
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
3226
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
3227
            } else {
3228
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
3229
                    $this->getClass(),
3230
                    $filterParameters['_sort_by'],
3231
                    []
3232
                );
3233
3234
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
3235
            }
3236
        }
3237
3238
        // initialize the datagrid
3239
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
3240
3241
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
3242
3243
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
3244
3245
        // build the datagrid filter
3246
        $this->configureDatagridFilters($mapper);
3247
3248
        // ok, try to limit to add parent filter
3249
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
3250
            $mapper->add($this->getParentAssociationMapping(), null, [
3251
                'show_filter' => false,
3252
                'label' => false,
3253
                'field_type' => ModelHiddenType::class,
3254
                'field_options' => [
3255
                    'model_manager' => $this->getModelManager(),
3256
                ],
3257
                'operator_type' => HiddenType::class,
3258
            ], null, null, [
3259
                'admin_code' => $this->getParent()->getCode(),
3260
            ]);
3261
        }
3262
3263
        foreach ($this->getExtensions() as $extension) {
3264
            $extension->configureDatagridFilters($mapper);
3265
        }
3266
    }
3267
3268
    /**
3269
     * Build all the related urls to the current admin.
3270
     */
3271
    private function buildRoutes()
3272
    {
3273
        if ($this->loaded['routes']) {
3274
            return;
3275
        }
3276
3277
        $this->loaded['routes'] = true;
3278
3279
        $this->routes = new RouteCollection(
3280
            $this->getBaseCodeRoute(),
3281
            $this->getBaseRouteName(),
3282
            $this->getBaseRoutePattern(),
3283
            $this->getBaseControllerName()
3284
        );
3285
3286
        $this->routeBuilder->build($this, $this->routes);
3287
3288
        $this->configureRoutes($this->routes);
3289
3290
        foreach ($this->getExtensions() as $extension) {
3291
            $extension->configureRoutes($this, $this->routes);
3292
        }
3293
    }
3294
}
3295