Completed
Push — 3.x ( f7534c...26d69b )
by Grégoire
04:05
created

AbstractAdmin::hasAccess()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

Loading history...
632
633
        $this->configure();
634
    }
635
636
    /**
637
     * {@inheritdoc}
638
     */
639
    public function configure()
640
    {
641
    }
642
643
    /**
644
     * {@inheritdoc}
645
     */
646
    public function update($object)
647
    {
648
        $this->preUpdate($object);
649
        foreach ($this->extensions as $extension) {
650
            $extension->preUpdate($this, $object);
651
        }
652
653
        $result = $this->getModelManager()->update($object);
654
        // BC compatibility
655
        if (null !== $result) {
656
            $object = $result;
657
        }
658
659
        $this->postUpdate($object);
660
        foreach ($this->extensions as $extension) {
661
            $extension->postUpdate($this, $object);
662
        }
663
664
        return $object;
665
    }
666
667
    /**
668
     * {@inheritdoc}
669
     */
670
    public function create($object)
671
    {
672
        $this->prePersist($object);
673
        foreach ($this->extensions as $extension) {
674
            $extension->prePersist($this, $object);
675
        }
676
677
        $result = $this->getModelManager()->create($object);
678
        // BC compatibility
679
        if (null !== $result) {
680
            $object = $result;
681
        }
682
683
        $this->postPersist($object);
684
        foreach ($this->extensions as $extension) {
685
            $extension->postPersist($this, $object);
686
        }
687
688
        $this->createObjectSecurity($object);
689
690
        return $object;
691
    }
692
693
    /**
694
     * {@inheritdoc}
695
     */
696
    public function delete($object)
697
    {
698
        $this->preRemove($object);
699
        foreach ($this->extensions as $extension) {
700
            $extension->preRemove($this, $object);
701
        }
702
703
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
704
        $this->getModelManager()->delete($object);
705
706
        $this->postRemove($object);
707
        foreach ($this->extensions as $extension) {
708
            $extension->postRemove($this, $object);
709
        }
710
    }
711
712
    /**
713
     * {@inheritdoc}
714
     */
715
    public function preValidate($object)
0 ignored issues
show
Unused Code introduced by
The parameter $object is not used and could be removed.

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

Loading history...
716
    {
717
    }
718
719
    /**
720
     * {@inheritdoc}
721
     */
722
    public function preUpdate($object)
723
    {
724
    }
725
726
    /**
727
     * {@inheritdoc}
728
     */
729
    public function postUpdate($object)
730
    {
731
    }
732
733
    /**
734
     * {@inheritdoc}
735
     */
736
    public function prePersist($object)
737
    {
738
    }
739
740
    /**
741
     * {@inheritdoc}
742
     */
743
    public function postPersist($object)
744
    {
745
    }
746
747
    /**
748
     * {@inheritdoc}
749
     */
750
    public function preRemove($object)
751
    {
752
    }
753
754
    /**
755
     * {@inheritdoc}
756
     */
757
    public function postRemove($object)
758
    {
759
    }
760
761
    /**
762
     * {@inheritdoc}
763
     */
764
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
765
    {
766
    }
767
768
    /**
769
     * {@inheritdoc}
770
     */
771
    public function getFilterParameters()
772
    {
773
        $parameters = array();
774
775
        // build the values array
776
        if ($this->hasRequest()) {
777
            $filters = $this->request->query->get('filter', array());
778
779
            // if persisting filters, save filters to session, or pull them out of session if no new filters set
780
            if ($this->persistFilters) {
781
                if ($filters == array() && $this->request->query->get('filters') != 'reset') {
782
                    $filters = $this->request->getSession()->get($this->getCode().'.filter.parameters', array());
783
                } else {
784
                    $this->request->getSession()->set($this->getCode().'.filter.parameters', $filters);
785
                }
786
            }
787
788
            $parameters = array_merge(
789
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
790
                $this->datagridValues,
791
                $this->getDefaultFilterValues(),
792
                $filters
793
            );
794
795
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
796
                $parameters['_per_page'] = $this->maxPerPage;
797
            }
798
799
            // always force the parent value
800
            if ($this->isChild() && $this->getParentAssociationMapping()) {
801
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
802
                $parameters[$name] = array('value' => $this->request->get($this->getParent()->getIdParameter()));
803
            }
804
        }
805
806
        return $parameters;
807
    }
808
809
    /**
810
     * {@inheritdoc}
811
     */
812
    public function buildDatagrid()
813
    {
814
        if ($this->datagrid) {
815
            return;
816
        }
817
818
        $filterParameters = $this->getFilterParameters();
819
820
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
821
        if (isset($filterParameters['_sort_by']) && is_string($filterParameters['_sort_by'])) {
822
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
823
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
824
            } else {
825
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
826
                    $this->getClass(),
827
                    $filterParameters['_sort_by'],
828
                    array()
829
                );
830
831
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
832
            }
833
        }
834
835
        // initialize the datagrid
836
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
837
838
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
839
840
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
841
842
        // build the datagrid filter
843
        $this->configureDatagridFilters($mapper);
844
845
        // ok, try to limit to add parent filter
846
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
847
            // NEXT_MAJOR: Keep FQCN when bumping Symfony requirement to 2.8+.
848
            $modelHiddenType = method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
849
                ? 'Sonata\AdminBundle\Form\Type\ModelHiddenType'
850
                : 'sonata_type_model_hidden';
851
852
            // NEXT_MAJOR: Keep FQCN when bumping Symfony requirement to 2.8+.
853
            $hiddenType = method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
854
                ? 'Symfony\Component\Form\Extension\Core\Type\HiddenType'
855
                : 'hidden';
856
857
            $mapper->add($this->getParentAssociationMapping(), null, array(
858
                'show_filter' => false,
859
                'label' => false,
860
                'field_type' => $modelHiddenType,
861
                'field_options' => array(
862
                    'model_manager' => $this->getModelManager(),
863
                ),
864
                'operator_type' => $hiddenType,
865
            ), null, null, array(
866
                'admin_code' => $this->getParent()->getCode(),
867
            ));
868
        }
869
870
        foreach ($this->getExtensions() as $extension) {
871
            $extension->configureDatagridFilters($mapper);
872
        }
873
    }
874
875
    /**
876
     * Returns the name of the parent related field, so the field can be use to set the default
877
     * value (ie the parent object) or to filter the object.
878
     *
879
     * @return string the name of the parent related field
880
     */
881
    public function getParentAssociationMapping()
882
    {
883
        return $this->parentAssociationMapping;
884
    }
885
886
    /**
887
     * Returns the baseRoutePattern used to generate the routing information.
888
     *
889
     * @throws \RuntimeException
890
     *
891
     * @return string the baseRoutePattern used to generate the routing information
892
     */
893
    public function getBaseRoutePattern()
894
    {
895
        if (null !== $this->cachedBaseRoutePattern) {
896
            return $this->cachedBaseRoutePattern;
897
        }
898
899
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
900
            if (!$this->baseRoutePattern) {
901
                preg_match(self::CLASS_REGEX, $this->class, $matches);
902
903
                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...
904
                    throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
905
                }
906
            }
907
908
            $this->cachedBaseRoutePattern = sprintf('%s/%s/%s',
909
                $this->getParent()->getBaseRoutePattern(),
910
                $this->getParent()->getRouterIdParameter(),
911
                $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...
912
            );
913
        } elseif ($this->baseRoutePattern) {
914
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
915
        } else {
916
            preg_match(self::CLASS_REGEX, $this->class, $matches);
917
918
            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...
919
                throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
920
            }
921
922
            $this->cachedBaseRoutePattern = sprintf('/%s%s/%s',
923
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
924
                $this->urlize($matches[3], '-'),
925
                $this->urlize($matches[5], '-')
926
            );
927
        }
928
929
        return $this->cachedBaseRoutePattern;
930
    }
931
932
    /**
933
     * Returns the baseRouteName used to generate the routing information.
934
     *
935
     * @throws \RuntimeException
936
     *
937
     * @return string the baseRouteName used to generate the routing information
938
     */
939
    public function getBaseRouteName()
940
    {
941
        if (null !== $this->cachedBaseRouteName) {
942
            return $this->cachedBaseRouteName;
943
        }
944
945
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
946
            if (!$this->baseRouteName) {
947
                preg_match(self::CLASS_REGEX, $this->class, $matches);
948
949
                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...
950
                    throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
951
                }
952
            }
953
954
            $this->cachedBaseRouteName = sprintf('%s_%s',
955
                $this->getParent()->getBaseRouteName(),
956
                $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...
957
            );
958
        } elseif ($this->baseRouteName) {
959
            $this->cachedBaseRouteName = $this->baseRouteName;
960
        } else {
961
            preg_match(self::CLASS_REGEX, $this->class, $matches);
962
963
            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...
964
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
965
            }
966
967
            $this->cachedBaseRouteName = sprintf('admin_%s%s_%s',
968
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
969
                $this->urlize($matches[3]),
970
                $this->urlize($matches[5])
971
            );
972
        }
973
974
        return $this->cachedBaseRouteName;
975
    }
976
977
    /**
978
     * urlize the given word.
979
     *
980
     * @param string $word
981
     * @param string $sep  the separator
982
     *
983
     * @return string
984
     */
985
    public function urlize($word, $sep = '_')
986
    {
987
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
988
    }
989
990
    /**
991
     * {@inheritdoc}
992
     */
993
    public function getClass()
994
    {
995
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
996
        if ($this->hasSubject() && is_object($this->getSubject())) {
997
            return ClassUtils::getClass($this->getSubject());
998
        }
999
1000
        if (!$this->hasActiveSubClass()) {
1001
            if (count($this->getSubClasses()) > 0) {
1002
                $subject = $this->getSubject();
1003
1004
                if ($subject && is_object($subject)) {
1005
                    return ClassUtils::getClass($subject);
1006
                }
1007
            }
1008
1009
            return $this->class;
1010
        }
1011
1012
        if ($this->getParentFieldDescription() && $this->hasActiveSubClass()) {
1013
            throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
1014
        }
1015
1016
        $subClass = $this->getRequest()->query->get('subclass');
1017
1018
        return $this->getSubClass($subClass);
1019
    }
1020
1021
    /**
1022
     * {@inheritdoc}
1023
     */
1024
    public function getSubClasses()
1025
    {
1026
        return $this->subClasses;
1027
    }
1028
1029
    /**
1030
     * {@inheritdoc}
1031
     */
1032
    public function addSubClass($subClass)
1033
    {
1034
        if (!in_array($subClass, $this->subClasses)) {
1035
            $this->subClasses[] = $subClass;
1036
        }
1037
    }
1038
1039
    /**
1040
     * {@inheritdoc}
1041
     */
1042
    public function setSubClasses(array $subClasses)
1043
    {
1044
        $this->subClasses = $subClasses;
1045
    }
1046
1047
    /**
1048
     * {@inheritdoc}
1049
     */
1050
    public function hasSubClass($name)
1051
    {
1052
        return isset($this->subClasses[$name]);
1053
    }
1054
1055
    /**
1056
     * {@inheritdoc}
1057
     */
1058
    public function hasActiveSubClass()
1059
    {
1060
        if (count($this->subClasses) > 0 && $this->request) {
1061
            return null !== $this->getRequest()->query->get('subclass');
1062
        }
1063
1064
        return false;
1065
    }
1066
1067
    /**
1068
     * {@inheritdoc}
1069
     */
1070
    public function getActiveSubClass()
1071
    {
1072
        if (!$this->hasActiveSubClass()) {
1073
            return;
1074
        }
1075
1076
        return $this->getClass();
1077
    }
1078
1079
    /**
1080
     * {@inheritdoc}
1081
     */
1082
    public function getActiveSubclassCode()
1083
    {
1084
        if (!$this->hasActiveSubClass()) {
1085
            return;
1086
        }
1087
1088
        $subClass = $this->getRequest()->query->get('subclass');
1089
1090
        if (!$this->hasSubClass($subClass)) {
1091
            return;
1092
        }
1093
1094
        return $subClass;
1095
    }
1096
1097
    /**
1098
     * {@inheritdoc}
1099
     */
1100
    public function getBatchActions()
1101
    {
1102
        $actions = array();
1103
1104
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1105
            $actions['delete'] = array(
1106
                'label' => 'action_delete',
1107
                'translation_domain' => 'SonataAdminBundle',
1108
                'ask_confirmation' => true, // by default always true
1109
            );
1110
        }
1111
1112
        $actions = $this->configureBatchActions($actions);
1113
1114
        foreach ($this->getExtensions() as $extension) {
1115
            // TODO: remove method check in next major release
1116
            if (method_exists($extension, 'configureBatchActions')) {
1117
                $actions = $extension->configureBatchActions($this, $actions);
1118
            }
1119
        }
1120
1121
        foreach ($actions  as $name => &$action) {
1122
            if (!array_key_exists('label', $action)) {
1123
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1124
            }
1125
1126
            if (!array_key_exists('translation_domain', $action)) {
1127
                $action['translation_domain'] = $this->getTranslationDomain();
1128
            }
1129
        }
1130
1131
        return $actions;
1132
    }
1133
1134
    /**
1135
     * {@inheritdoc}
1136
     */
1137
    public function getRoutes()
1138
    {
1139
        $this->buildRoutes();
1140
1141
        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...
1142
    }
1143
1144
    /**
1145
     * {@inheritdoc}
1146
     */
1147
    public function getRouterIdParameter()
1148
    {
1149
        return '{'.$this->getIdParameter().'}';
1150
    }
1151
1152
    /**
1153
     * {@inheritdoc}
1154
     */
1155
    public function getIdParameter()
1156
    {
1157
        $parameter = 'id';
1158
1159
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1160
            $parameter = 'child'.ucfirst($parameter);
1161
        }
1162
1163
        return $parameter;
1164
    }
1165
1166
    /**
1167
     * {@inheritdoc}
1168
     */
1169
    public function hasRoute($name)
1170
    {
1171
        if (!$this->routeGenerator) {
1172
            throw new \RuntimeException('RouteGenerator cannot be null');
1173
        }
1174
1175
        return $this->routeGenerator->hasAdminRoute($this, $name);
1176
    }
1177
1178
    /**
1179
     * {@inheritdoc}
1180
     */
1181
    public function isCurrentRoute($name, $adminCode = null)
1182
    {
1183
        if (!$this->hasRequest()) {
1184
            return false;
1185
        }
1186
1187
        $request = $this->getRequest();
1188
        $route = $request->get('_route');
1189
1190
        if ($adminCode) {
1191
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1192
        } else {
1193
            $admin = $this;
1194
        }
1195
1196
        if (!$admin) {
1197
            return false;
1198
        }
1199
1200
        return ($admin->getBaseRouteName().'_'.$name) == $route;
1201
    }
1202
1203
    /**
1204
     * {@inheritdoc}
1205
     */
1206
    public function generateObjectUrl($name, $object, array $parameters = array(), $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1207
    {
1208
        $parameters['id'] = $this->getUrlsafeIdentifier($object);
1209
1210
        return $this->generateUrl($name, $parameters, $absolute);
1211
    }
1212
1213
    /**
1214
     * {@inheritdoc}
1215
     */
1216
    public function generateUrl($name, array $parameters = array(), $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1217
    {
1218
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $absolute);
1219
    }
1220
1221
    /**
1222
     * {@inheritdoc}
1223
     */
1224
    public function generateMenuUrl($name, array $parameters = array(), $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1225
    {
1226
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $absolute);
1227
    }
1228
1229
    /**
1230
     * @param array $templates
1231
     */
1232
    public function setTemplates(array $templates)
1233
    {
1234
        $this->templates = $templates;
1235
    }
1236
1237
    /**
1238
     * @param string $name
1239
     * @param string $template
1240
     */
1241
    public function setTemplate($name, $template)
1242
    {
1243
        $this->templates[$name] = $template;
1244
    }
1245
1246
    /**
1247
     * @return array
1248
     */
1249
    public function getTemplates()
1250
    {
1251
        return $this->templates;
1252
    }
1253
1254
    /**
1255
     * {@inheritdoc}
1256
     */
1257
    public function getTemplate($name)
1258
    {
1259
        if (isset($this->templates[$name])) {
1260
            return $this->templates[$name];
1261
        }
1262
    }
1263
1264
    /**
1265
     * {@inheritdoc}
1266
     */
1267
    public function getNewInstance()
1268
    {
1269
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1270
        foreach ($this->getExtensions() as $extension) {
1271
            $extension->alterNewInstance($this, $object);
1272
        }
1273
1274
        return $object;
1275
    }
1276
1277
    /**
1278
     * {@inheritdoc}
1279
     */
1280
    public function getFormBuilder()
1281
    {
1282
        $this->formOptions['data_class'] = $this->getClass();
1283
1284
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1285
            $this->getUniqid(),
1286
            $this->formOptions
1287
        );
1288
1289
        $this->defineFormBuilder($formBuilder);
1290
1291
        return $formBuilder;
1292
    }
1293
1294
    /**
1295
     * This method is being called by the main admin class and the child class,
1296
     * the getFormBuilder is only call by the main admin class.
1297
     *
1298
     * @param FormBuilderInterface $formBuilder
1299
     */
1300
    public function defineFormBuilder(FormBuilderInterface $formBuilder)
1301
    {
1302
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1303
1304
        $this->configureFormFields($mapper);
1305
1306
        foreach ($this->getExtensions() as $extension) {
1307
            $extension->configureFormFields($mapper);
1308
        }
1309
1310
        $this->attachInlineValidator();
1311
    }
1312
1313
    /**
1314
     * {@inheritdoc}
1315
     */
1316
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
1317
    {
1318
        $pool = $this->getConfigurationPool();
1319
1320
        $adminCode = $fieldDescription->getOption('admin_code');
1321
1322
        if ($adminCode !== null) {
1323
            $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...
1324
        } else {
1325
            $admin = $pool->getAdminByClass($fieldDescription->getTargetEntity());
1326
        }
1327
1328
        if (!$admin) {
1329
            return;
1330
        }
1331
1332
        if ($this->hasRequest()) {
1333
            $admin->setRequest($this->getRequest());
1334
        }
1335
1336
        $fieldDescription->setAssociationAdmin($admin);
1337
    }
1338
1339
    /**
1340
     * {@inheritdoc}
1341
     */
1342
    public function getObject($id)
1343
    {
1344
        $object = $this->getModelManager()->find($this->getClass(), $id);
1345
        foreach ($this->getExtensions() as $extension) {
1346
            $extension->alterObject($this, $object);
1347
        }
1348
1349
        return $object;
1350
    }
1351
1352
    /**
1353
     * {@inheritdoc}
1354
     */
1355
    public function getForm()
1356
    {
1357
        $this->buildForm();
1358
1359
        return $this->form;
1360
    }
1361
1362
    /**
1363
     * {@inheritdoc}
1364
     */
1365
    public function getList()
1366
    {
1367
        $this->buildList();
1368
1369
        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...
1370
    }
1371
1372
    /**
1373
     * {@inheritdoc}
1374
     */
1375
    public function createQuery($context = 'list')
1376
    {
1377
        if (func_num_args() > 0) {
1378
            @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...
1379
                'The $context argument of '.__METHOD__.' is deprecated since 3.3, to be removed in 4.0.',
1380
                E_USER_DEPRECATED
1381
            );
1382
        }
1383
        $query = $this->getModelManager()->createQuery($this->class);
1384
1385
        foreach ($this->extensions as $extension) {
1386
            $extension->configureQuery($this, $query, $context);
1387
        }
1388
1389
        return $query;
1390
    }
1391
1392
    /**
1393
     * {@inheritdoc}
1394
     */
1395
    public function getDatagrid()
1396
    {
1397
        $this->buildDatagrid();
1398
1399
        return $this->datagrid;
1400
    }
1401
1402
    /**
1403
     * {@inheritdoc}
1404
     */
1405
    public function buildTabMenu($action, AdminInterface $childAdmin = null)
1406
    {
1407
        if ($this->loaded['tab_menu']) {
1408
            return;
1409
        }
1410
1411
        $this->loaded['tab_menu'] = true;
1412
1413
        $menu = $this->menuFactory->createItem('root');
1414
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1415
        $menu->setExtra('translation_domain', $this->translationDomain);
1416
1417
        // Prevents BC break with KnpMenuBundle v1.x
1418
        if (method_exists($menu, 'setCurrentUri')) {
1419
            $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...
1420
        }
1421
1422
        $this->configureTabMenu($menu, $action, $childAdmin);
1423
1424
        foreach ($this->getExtensions() as $extension) {
1425
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1426
        }
1427
1428
        $this->menu = $menu;
1429
    }
1430
1431
    /**
1432
     * {@inheritdoc}
1433
     */
1434
    public function buildSideMenu($action, AdminInterface $childAdmin = null)
1435
    {
1436
        return $this->buildTabMenu($action, $childAdmin);
1437
    }
1438
1439
    /**
1440
     * @param string         $action
1441
     * @param AdminInterface $childAdmin
1442
     *
1443
     * @return ItemInterface
1444
     */
1445
    public function getSideMenu($action, AdminInterface $childAdmin = null)
1446
    {
1447
        if ($this->isChild()) {
1448
            return $this->getParent()->getSideMenu($action, $this);
1449
        }
1450
1451
        $this->buildSideMenu($action, $childAdmin);
1452
1453
        return $this->menu;
1454
    }
1455
1456
    /**
1457
     * Returns the root code.
1458
     *
1459
     * @return string the root code
1460
     */
1461
    public function getRootCode()
1462
    {
1463
        return $this->getRoot()->getCode();
1464
    }
1465
1466
    /**
1467
     * Returns the master admin.
1468
     *
1469
     * @return AbstractAdmin the root admin class
1470
     */
1471
    public function getRoot()
1472
    {
1473
        $parentFieldDescription = $this->getParentFieldDescription();
1474
1475
        if (!$parentFieldDescription) {
1476
            return $this;
1477
        }
1478
1479
        return $parentFieldDescription->getAdmin()->getRoot();
1480
    }
1481
1482
    /**
1483
     * {@inheritdoc}
1484
     */
1485
    public function setBaseControllerName($baseControllerName)
1486
    {
1487
        $this->baseControllerName = $baseControllerName;
1488
    }
1489
1490
    /**
1491
     * {@inheritdoc}
1492
     */
1493
    public function getBaseControllerName()
1494
    {
1495
        return $this->baseControllerName;
1496
    }
1497
1498
    /**
1499
     * @param string $label
1500
     */
1501
    public function setLabel($label)
1502
    {
1503
        $this->label = $label;
1504
    }
1505
1506
    /**
1507
     * {@inheritdoc}
1508
     */
1509
    public function getLabel()
1510
    {
1511
        return $this->label;
1512
    }
1513
1514
    /**
1515
     * @param bool $persist
1516
     */
1517
    public function setPersistFilters($persist)
1518
    {
1519
        $this->persistFilters = $persist;
1520
    }
1521
1522
    /**
1523
     * @param int $maxPerPage
1524
     */
1525
    public function setMaxPerPage($maxPerPage)
1526
    {
1527
        $this->maxPerPage = $maxPerPage;
1528
    }
1529
1530
    /**
1531
     * @return int
1532
     */
1533
    public function getMaxPerPage()
1534
    {
1535
        return $this->maxPerPage;
1536
    }
1537
1538
    /**
1539
     * @param int $maxPageLinks
1540
     */
1541
    public function setMaxPageLinks($maxPageLinks)
1542
    {
1543
        $this->maxPageLinks = $maxPageLinks;
1544
    }
1545
1546
    /**
1547
     * @return int
1548
     */
1549
    public function getMaxPageLinks()
1550
    {
1551
        return $this->maxPageLinks;
1552
    }
1553
1554
    /**
1555
     * {@inheritdoc}
1556
     */
1557
    public function getFormGroups()
1558
    {
1559
        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 1559 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getFormGroups of type array.
Loading history...
1560
    }
1561
1562
    /**
1563
     * {@inheritdoc}
1564
     */
1565
    public function setFormGroups(array $formGroups)
1566
    {
1567
        $this->formGroups = $formGroups;
1568
    }
1569
1570
    /**
1571
     * {@inheritdoc}
1572
     */
1573
    public function removeFieldFromFormGroup($key)
1574
    {
1575
        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...
1576
            unset($this->formGroups[$name]['fields'][$key]);
1577
1578
            if (empty($this->formGroups[$name]['fields'])) {
1579
                unset($this->formGroups[$name]);
1580
            }
1581
        }
1582
    }
1583
1584
    /**
1585
     * @param array $group
1586
     * @param array $keys
1587
     */
1588
    public function reorderFormGroup($group, array $keys)
1589
    {
1590
        $formGroups = $this->getFormGroups();
1591
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1592
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups() on line 1590 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...
1593
    }
1594
1595
    /**
1596
     * {@inheritdoc}
1597
     */
1598
    public function getFormTabs()
1599
    {
1600
        return $this->formTabs;
1601
    }
1602
1603
    /**
1604
     * {@inheritdoc}
1605
     */
1606
    public function setFormTabs(array $formTabs)
1607
    {
1608
        $this->formTabs = $formTabs;
1609
    }
1610
1611
    /**
1612
     * {@inheritdoc}
1613
     */
1614
    public function getShowTabs()
1615
    {
1616
        return $this->showTabs;
1617
    }
1618
1619
    /**
1620
     * {@inheritdoc}
1621
     */
1622
    public function setShowTabs(array $showTabs)
1623
    {
1624
        $this->showTabs = $showTabs;
1625
    }
1626
1627
    /**
1628
     * {@inheritdoc}
1629
     */
1630
    public function getShowGroups()
1631
    {
1632
        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 1632 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getShowGroups of type array.
Loading history...
1633
    }
1634
1635
    /**
1636
     * {@inheritdoc}
1637
     */
1638
    public function setShowGroups(array $showGroups)
1639
    {
1640
        $this->showGroups = $showGroups;
1641
    }
1642
1643
    /**
1644
     * {@inheritdoc}
1645
     */
1646
    public function reorderShowGroup($group, array $keys)
1647
    {
1648
        $showGroups = $this->getShowGroups();
1649
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1650
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups() on line 1648 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...
1651
    }
1652
1653
    /**
1654
     * {@inheritdoc}
1655
     */
1656
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
1657
    {
1658
        $this->parentFieldDescription = $parentFieldDescription;
1659
    }
1660
1661
    /**
1662
     * {@inheritdoc}
1663
     */
1664
    public function getParentFieldDescription()
1665
    {
1666
        return $this->parentFieldDescription;
1667
    }
1668
1669
    /**
1670
     * {@inheritdoc}
1671
     */
1672
    public function hasParentFieldDescription()
1673
    {
1674
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1675
    }
1676
1677
    /**
1678
     * {@inheritdoc}
1679
     */
1680
    public function setSubject($subject)
1681
    {
1682
        if (is_object($subject) && !is_a($subject, $this->class, true)) {
1683
            $message = <<<'EOT'
1684
You are trying to set entity an instance of "%s",
1685
which is not the one registered with this admin class ("%s").
1686
This is deprecated since 3.5 and will no longer be supported in 4.0.
1687
EOT;
1688
1689
            @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...
1690
                sprintf($message, get_class($subject), $this->class),
1691
                E_USER_DEPRECATED
1692
            ); // NEXT_MAJOR : throw an exception instead
1693
        }
1694
1695
        $this->subject = $subject;
1696
    }
1697
1698
    /**
1699
     * {@inheritdoc}
1700
     */
1701
    public function getSubject()
1702
    {
1703
        if ($this->subject === null && $this->request && !$this->hasParentFieldDescription()) {
1704
            $id = $this->request->get($this->getIdParameter());
1705
            $this->subject = $this->getModelManager()->find($this->class, $id);
1706
        }
1707
1708
        return $this->subject;
1709
    }
1710
1711
    /**
1712
     * {@inheritdoc}
1713
     */
1714
    public function hasSubject()
1715
    {
1716
        return $this->subject != null;
1717
    }
1718
1719
    /**
1720
     * {@inheritdoc}
1721
     */
1722
    public function getFormFieldDescriptions()
1723
    {
1724
        $this->buildForm();
1725
1726
        return $this->formFieldDescriptions;
1727
    }
1728
1729
    /**
1730
     * {@inheritdoc}
1731
     */
1732
    public function getFormFieldDescription($name)
1733
    {
1734
        return $this->hasFormFieldDescription($name) ? $this->formFieldDescriptions[$name] : null;
1735
    }
1736
1737
    /**
1738
     * Returns true if the admin has a FieldDescription with the given $name.
1739
     *
1740
     * @param string $name
1741
     *
1742
     * @return bool
1743
     */
1744
    public function hasFormFieldDescription($name)
1745
    {
1746
        return array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1747
    }
1748
1749
    /**
1750
     * {@inheritdoc}
1751
     */
1752
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1753
    {
1754
        $this->formFieldDescriptions[$name] = $fieldDescription;
1755
    }
1756
1757
    /**
1758
     * remove a FieldDescription.
1759
     *
1760
     * @param string $name
1761
     */
1762
    public function removeFormFieldDescription($name)
1763
    {
1764
        unset($this->formFieldDescriptions[$name]);
1765
    }
1766
1767
    /**
1768
     * build and return the collection of form FieldDescription.
1769
     *
1770
     * @return array collection of form FieldDescription
1771
     */
1772
    public function getShowFieldDescriptions()
1773
    {
1774
        $this->buildShow();
1775
1776
        return $this->showFieldDescriptions;
1777
    }
1778
1779
    /**
1780
     * Returns the form FieldDescription with the given $name.
1781
     *
1782
     * @param string $name
1783
     *
1784
     * @return FieldDescriptionInterface
1785
     */
1786
    public function getShowFieldDescription($name)
1787
    {
1788
        $this->buildShow();
1789
1790
        return $this->hasShowFieldDescription($name) ? $this->showFieldDescriptions[$name] : null;
1791
    }
1792
1793
    /**
1794
     * {@inheritdoc}
1795
     */
1796
    public function hasShowFieldDescription($name)
1797
    {
1798
        return array_key_exists($name, $this->showFieldDescriptions);
1799
    }
1800
1801
    /**
1802
     * {@inheritdoc}
1803
     */
1804
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1805
    {
1806
        $this->showFieldDescriptions[$name] = $fieldDescription;
1807
    }
1808
1809
    /**
1810
     * {@inheritdoc}
1811
     */
1812
    public function removeShowFieldDescription($name)
1813
    {
1814
        unset($this->showFieldDescriptions[$name]);
1815
    }
1816
1817
    /**
1818
     * {@inheritdoc}
1819
     */
1820
    public function getListFieldDescriptions()
1821
    {
1822
        $this->buildList();
1823
1824
        return $this->listFieldDescriptions;
1825
    }
1826
1827
    /**
1828
     * {@inheritdoc}
1829
     */
1830
    public function getListFieldDescription($name)
1831
    {
1832
        return $this->hasListFieldDescription($name) ? $this->listFieldDescriptions[$name] : null;
1833
    }
1834
1835
    /**
1836
     * {@inheritdoc}
1837
     */
1838
    public function hasListFieldDescription($name)
1839
    {
1840
        $this->buildList();
1841
1842
        return array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1843
    }
1844
1845
    /**
1846
     * {@inheritdoc}
1847
     */
1848
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1849
    {
1850
        $this->listFieldDescriptions[$name] = $fieldDescription;
1851
    }
1852
1853
    /**
1854
     * {@inheritdoc}
1855
     */
1856
    public function removeListFieldDescription($name)
1857
    {
1858
        unset($this->listFieldDescriptions[$name]);
1859
    }
1860
1861
    /**
1862
     * {@inheritdoc}
1863
     */
1864
    public function getFilterFieldDescription($name)
1865
    {
1866
        return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null;
1867
    }
1868
1869
    /**
1870
     * {@inheritdoc}
1871
     */
1872
    public function hasFilterFieldDescription($name)
1873
    {
1874
        return array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1875
    }
1876
1877
    /**
1878
     * {@inheritdoc}
1879
     */
1880
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1881
    {
1882
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1883
    }
1884
1885
    /**
1886
     * {@inheritdoc}
1887
     */
1888
    public function removeFilterFieldDescription($name)
1889
    {
1890
        unset($this->filterFieldDescriptions[$name]);
1891
    }
1892
1893
    /**
1894
     * {@inheritdoc}
1895
     */
1896
    public function getFilterFieldDescriptions()
1897
    {
1898
        $this->buildDatagrid();
1899
1900
        return $this->filterFieldDescriptions;
1901
    }
1902
1903
    /**
1904
     * {@inheritdoc}
1905
     */
1906
    public function addChild(AdminInterface $child)
1907
    {
1908
        for ($parentAdmin = $this; null !== $parentAdmin; $parentAdmin = $parentAdmin->getParent()) {
1909
            if ($parentAdmin->getCode() !== $child->getCode()) {
1910
                continue;
1911
            }
1912
1913
            throw new \RuntimeException(sprintf(
1914
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1915
                $child->getCode(), $this->getCode()
1916
            ));
1917
        }
1918
1919
        $this->children[$child->getCode()] = $child;
1920
1921
        $child->setParent($this);
1922
    }
1923
1924
    /**
1925
     * {@inheritdoc}
1926
     */
1927
    public function hasChild($code)
1928
    {
1929
        return isset($this->children[$code]);
1930
    }
1931
1932
    /**
1933
     * {@inheritdoc}
1934
     */
1935
    public function getChildren()
1936
    {
1937
        return $this->children;
1938
    }
1939
1940
    /**
1941
     * {@inheritdoc}
1942
     */
1943
    public function getChild($code)
1944
    {
1945
        return $this->hasChild($code) ? $this->children[$code] : null;
1946
    }
1947
1948
    /**
1949
     * {@inheritdoc}
1950
     */
1951
    public function setParent(AdminInterface $parent)
1952
    {
1953
        $this->parent = $parent;
1954
    }
1955
1956
    /**
1957
     * {@inheritdoc}
1958
     */
1959
    public function getParent()
1960
    {
1961
        return $this->parent;
1962
    }
1963
1964
    /**
1965
     * {@inheritdoc}
1966
     */
1967
    final public function getRootAncestor()
1968
    {
1969
        $parent = $this;
1970
1971
        while ($parent->isChild()) {
1972
            $parent = $parent->getParent();
1973
        }
1974
1975
        return $parent;
1976
    }
1977
1978
    /**
1979
     * {@inheritdoc}
1980
     */
1981
    final public function getChildDepth()
1982
    {
1983
        $parent = $this;
1984
        $depth = 0;
1985
1986
        while ($parent->isChild()) {
1987
            $parent = $parent->getParent();
1988
            ++$depth;
1989
        }
1990
1991
        return $depth;
1992
    }
1993
1994
    /**
1995
     * {@inheritdoc}
1996
     */
1997
    final public function getCurrentLeafChildAdmin()
1998
    {
1999
        $child = $this->getCurrentChildAdmin();
2000
2001
        if (null === $child) {
2002
            return;
2003
        }
2004
2005
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
2006
            $child = $c;
2007
        }
2008
2009
        return $child;
2010
    }
2011
2012
    /**
2013
     * {@inheritdoc}
2014
     */
2015
    public function isChild()
2016
    {
2017
        return $this->parent instanceof AdminInterface;
2018
    }
2019
2020
    /**
2021
     * Returns true if the admin has children, false otherwise.
2022
     *
2023
     * @return bool if the admin has children
2024
     */
2025
    public function hasChildren()
2026
    {
2027
        return count($this->children) > 0;
2028
    }
2029
2030
    /**
2031
     * {@inheritdoc}
2032
     */
2033
    public function setUniqid($uniqid)
2034
    {
2035
        $this->uniqid = $uniqid;
2036
    }
2037
2038
    /**
2039
     * {@inheritdoc}
2040
     */
2041
    public function getUniqid()
2042
    {
2043
        if (!$this->uniqid) {
2044
            $this->uniqid = 's'.uniqid();
2045
        }
2046
2047
        return $this->uniqid;
2048
    }
2049
2050
    /**
2051
     * Returns the classname label.
2052
     *
2053
     * @return string the classname label
2054
     */
2055
    public function getClassnameLabel()
2056
    {
2057
        return $this->classnameLabel;
2058
    }
2059
2060
    /**
2061
     * {@inheritdoc}
2062
     */
2063
    public function getPersistentParameters()
2064
    {
2065
        $parameters = array();
2066
2067
        foreach ($this->getExtensions() as $extension) {
2068
            $params = $extension->getPersistentParameters($this);
2069
2070
            if (!is_array($params)) {
2071
                throw new \RuntimeException(sprintf('The %s::getPersistentParameters must return an array', get_class($extension)));
2072
            }
2073
2074
            $parameters = array_merge($parameters, $params);
2075
        }
2076
2077
        return $parameters;
2078
    }
2079
2080
    /**
2081
     * @param string $name
2082
     *
2083
     * @return null|mixed
2084
     */
2085
    public function getPersistentParameter($name)
2086
    {
2087
        $parameters = $this->getPersistentParameters();
2088
2089
        return isset($parameters[$name]) ? $parameters[$name] : null;
2090
    }
2091
2092
    /**
2093
     * {@inheritdoc}
2094
     */
2095
    public function getBreadcrumbs($action)
2096
    {
2097
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2098
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2099
            ' Use Sonata\AdminBundle\Admin\BreadcrumbsBuilder::getBreadcrumbs instead.',
2100
            E_USER_DEPRECATED
2101
        );
2102
2103
        return $this->getBreadcrumbsBuilder()->getBreadcrumbs($this, $action);
2104
    }
2105
2106
    /**
2107
     * Generates the breadcrumbs array.
2108
     *
2109
     * Note: the method will be called by the top admin instance (parent => child)
2110
     *
2111
     * @param string             $action
2112
     * @param ItemInterface|null $menu
2113
     *
2114
     * @return array
2115
     */
2116
    public function buildBreadcrumbs($action, MenuItemInterface $menu = null)
2117
    {
2118
        @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...
2119
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.',
2120
            E_USER_DEPRECATED
2121
        );
2122
2123
        if (isset($this->breadcrumbs[$action])) {
2124
            return $this->breadcrumbs[$action];
2125
        }
2126
2127
        return $this->breadcrumbs[$action] = $this->getBreadcrumbsBuilder()
2128
            ->buildBreadcrumbs($this, $action, $menu);
2129
    }
2130
2131
    /**
2132
     * NEXT_MAJOR : remove this method.
2133
     *
2134
     * @return BreadcrumbsBuilderInterface
2135
     */
2136
    final public function getBreadcrumbsBuilder()
2137
    {
2138
        @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...
2139
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2140
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
2141
            E_USER_DEPRECATED
2142
        );
2143
        if ($this->breadcrumbsBuilder === null) {
2144
            $this->breadcrumbsBuilder = new BreadcrumbsBuilder(
2145
                $this->getConfigurationPool()->getContainer()->getParameter('sonata.admin.configuration.breadcrumbs')
2146
            );
2147
        }
2148
2149
        return $this->breadcrumbsBuilder;
2150
    }
2151
2152
    /**
2153
     * NEXT_MAJOR : remove this method.
2154
     *
2155
     * @param BreadcrumbsBuilderInterface
2156
     *
2157
     * @return AbstractAdmin
2158
     */
2159
    final public function setBreadcrumbsBuilder(BreadcrumbsBuilderInterface $value)
2160
    {
2161
        @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...
2162
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2163
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
2164
            E_USER_DEPRECATED
2165
        );
2166
        $this->breadcrumbsBuilder = $value;
2167
2168
        return $this;
2169
    }
2170
2171
    /**
2172
     * {@inheritdoc}
2173
     */
2174
    public function setCurrentChild($currentChild)
2175
    {
2176
        $this->currentChild = $currentChild;
2177
    }
2178
2179
    /**
2180
     * {@inheritdoc}
2181
     */
2182
    public function getCurrentChild()
2183
    {
2184
        return $this->currentChild;
2185
    }
2186
2187
    /**
2188
     * Returns the current child admin instance.
2189
     *
2190
     * @return AdminInterface|null the current child admin instance
2191
     */
2192
    public function getCurrentChildAdmin()
2193
    {
2194
        foreach ($this->children as $children) {
2195
            if ($children->getCurrentChild()) {
2196
                return $children;
2197
            }
2198
        }
2199
2200
        return;
2201
    }
2202
2203
    /**
2204
     * {@inheritdoc}
2205
     */
2206
    public function trans($id, array $parameters = array(), $domain = null, $locale = null)
2207
    {
2208
        @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...
2209
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2210
            E_USER_DEPRECATED
2211
        );
2212
2213
        $domain = $domain ?: $this->getTranslationDomain();
2214
2215
        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...
2216
    }
2217
2218
    /**
2219
     * Translate a message id.
2220
     *
2221
     * NEXT_MAJOR: remove this method
2222
     *
2223
     * @param string      $id
2224
     * @param int         $count
2225
     * @param array       $parameters
2226
     * @param string|null $domain
2227
     * @param string|null $locale
2228
     *
2229
     * @return string the translated string
2230
     *
2231
     * @deprecated since 3.9, to be removed with 4.0
2232
     */
2233
    public function transChoice($id, $count, array $parameters = array(), $domain = null, $locale = null)
2234
    {
2235
        @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...
2236
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2237
            E_USER_DEPRECATED
2238
        );
2239
2240
        $domain = $domain ?: $this->getTranslationDomain();
2241
2242
        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...
2243
    }
2244
2245
    /**
2246
     * {@inheritdoc}
2247
     */
2248
    public function setTranslationDomain($translationDomain)
2249
    {
2250
        $this->translationDomain = $translationDomain;
2251
    }
2252
2253
    /**
2254
     * {@inheritdoc}
2255
     */
2256
    public function getTranslationDomain()
2257
    {
2258
        return $this->translationDomain;
2259
    }
2260
2261
    /**
2262
     * {@inheritdoc}
2263
     *
2264
     * NEXT_MAJOR: remove this method
2265
     *
2266
     * @deprecated since 3.9, to be removed with 4.0
2267
     */
2268
    public function setTranslator(TranslatorInterface $translator)
2269
    {
2270
        $args = func_get_args();
2271
        if (isset($args[1]) && $args[1]) {
2272
            @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...
2273
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2274
                E_USER_DEPRECATED
2275
            );
2276
        }
2277
2278
        $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...
2279
    }
2280
2281
    /**
2282
     * {@inheritdoc}
2283
     *
2284
     * NEXT_MAJOR: remove this method
2285
     *
2286
     * @deprecated since 3.9, to be removed with 4.0
2287
     */
2288
    public function getTranslator()
2289
    {
2290
        @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...
2291
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2292
            E_USER_DEPRECATED
2293
        );
2294
2295
        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...
2296
    }
2297
2298
    /**
2299
     * {@inheritdoc}
2300
     */
2301
    public function getTranslationLabel($label, $context = '', $type = '')
2302
    {
2303
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2304
    }
2305
2306
    /**
2307
     * {@inheritdoc}
2308
     */
2309
    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...
2310
    {
2311
        $this->request = $request;
2312
2313
        foreach ($this->getChildren() as $children) {
2314
            $children->setRequest($request);
2315
        }
2316
    }
2317
2318
    /**
2319
     * {@inheritdoc}
2320
     */
2321
    public function getRequest()
2322
    {
2323
        if (!$this->request) {
2324
            throw new \RuntimeException('The Request object has not been set');
2325
        }
2326
2327
        return $this->request;
2328
    }
2329
2330
    /**
2331
     * {@inheritdoc}
2332
     */
2333
    public function hasRequest()
2334
    {
2335
        return $this->request !== null;
2336
    }
2337
2338
    /**
2339
     * {@inheritdoc}
2340
     */
2341
    public function setFormContractor(FormContractorInterface $formBuilder)
2342
    {
2343
        $this->formContractor = $formBuilder;
2344
    }
2345
2346
    /**
2347
     * @return FormContractorInterface
2348
     */
2349
    public function getFormContractor()
2350
    {
2351
        return $this->formContractor;
2352
    }
2353
2354
    /**
2355
     * {@inheritdoc}
2356
     */
2357
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder)
2358
    {
2359
        $this->datagridBuilder = $datagridBuilder;
2360
    }
2361
2362
    /**
2363
     * {@inheritdoc}
2364
     */
2365
    public function getDatagridBuilder()
2366
    {
2367
        return $this->datagridBuilder;
2368
    }
2369
2370
    /**
2371
     * {@inheritdoc}
2372
     */
2373
    public function setListBuilder(ListBuilderInterface $listBuilder)
2374
    {
2375
        $this->listBuilder = $listBuilder;
2376
    }
2377
2378
    /**
2379
     * {@inheritdoc}
2380
     */
2381
    public function getListBuilder()
2382
    {
2383
        return $this->listBuilder;
2384
    }
2385
2386
    /**
2387
     * @param ShowBuilderInterface $showBuilder
2388
     */
2389
    public function setShowBuilder(ShowBuilderInterface $showBuilder)
2390
    {
2391
        $this->showBuilder = $showBuilder;
2392
    }
2393
2394
    /**
2395
     * @return ShowBuilderInterface
2396
     */
2397
    public function getShowBuilder()
2398
    {
2399
        return $this->showBuilder;
2400
    }
2401
2402
    /**
2403
     * {@inheritdoc}
2404
     */
2405
    public function setConfigurationPool(Pool $configurationPool)
2406
    {
2407
        $this->configurationPool = $configurationPool;
2408
    }
2409
2410
    /**
2411
     * @return Pool
2412
     */
2413
    public function getConfigurationPool()
2414
    {
2415
        return $this->configurationPool;
2416
    }
2417
2418
    /**
2419
     * {@inheritdoc}
2420
     */
2421
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator)
2422
    {
2423
        $this->routeGenerator = $routeGenerator;
2424
    }
2425
2426
    /**
2427
     * @return RouteGeneratorInterface
2428
     */
2429
    public function getRouteGenerator()
2430
    {
2431
        return $this->routeGenerator;
2432
    }
2433
2434
    /**
2435
     * {@inheritdoc}
2436
     */
2437
    public function getCode()
2438
    {
2439
        return $this->code;
2440
    }
2441
2442
    /**
2443
     * NEXT_MAJOR: Remove this function.
2444
     *
2445
     * @deprecated This method is deprecated since 3.x and will be removed in 4.0
2446
     *
2447
     * @param string $baseCodeRoute
2448
     */
2449
    public function setBaseCodeRoute($baseCodeRoute)
2450
    {
2451
        @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...
2452
            'The '.__METHOD__.' is deprecated since 3.x and will be removed in 4.0.',
2453
            E_USER_DEPRECATED
2454
        );
2455
2456
        $this->baseCodeRoute = $baseCodeRoute;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since 3.x and will be removed in 4.0

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

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

Loading history...
2457
    }
2458
2459
    /**
2460
     * {@inheritdoc}
2461
     */
2462
    public function getBaseCodeRoute()
2463
    {
2464
        // NEXT_MAJOR: Uncomment the following lines.
2465
        // if ($this->isChild()) {
2466
        //     return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2467
        // }
2468
        //
2469
        // return $this->getCode();
2470
2471
        // NEXT_MAJOR: Remove all the code below.
2472
        if ($this->isChild()) {
2473
            $parentCode = $this->getParent()->getCode();
2474
2475
            if ($this->getParent()->isChild()) {
2476
                $parentCode = $this->getParent()->getBaseCodeRoute();
2477
            }
2478
2479
            return $parentCode.'|'.$this->getCode();
2480
        }
2481
2482
        return $this->baseCodeRoute;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since 3.x and will be removed in 4.0

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

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

Loading history...
2483
    }
2484
2485
    /**
2486
     * {@inheritdoc}
2487
     */
2488
    public function getModelManager()
2489
    {
2490
        return $this->modelManager;
2491
    }
2492
2493
    /**
2494
     * @param ModelManagerInterface $modelManager
2495
     */
2496
    public function setModelManager(ModelManagerInterface $modelManager)
2497
    {
2498
        $this->modelManager = $modelManager;
2499
    }
2500
2501
    /**
2502
     * {@inheritdoc}
2503
     */
2504
    public function getManagerType()
2505
    {
2506
        return $this->managerType;
2507
    }
2508
2509
    /**
2510
     * @param string $type
2511
     */
2512
    public function setManagerType($type)
2513
    {
2514
        $this->managerType = $type;
2515
    }
2516
2517
    /**
2518
     * {@inheritdoc}
2519
     */
2520
    public function getObjectIdentifier()
2521
    {
2522
        return $this->getCode();
2523
    }
2524
2525
    /**
2526
     * Set the roles and permissions per role.
2527
     *
2528
     * @param array $information
2529
     */
2530
    public function setSecurityInformation(array $information)
2531
    {
2532
        $this->securityInformation = $information;
2533
    }
2534
2535
    /**
2536
     * {@inheritdoc}
2537
     */
2538
    public function getSecurityInformation()
2539
    {
2540
        return $this->securityInformation;
2541
    }
2542
2543
    /**
2544
     * Return the list of permissions the user should have in order to display the admin.
2545
     *
2546
     * @param string $context
2547
     *
2548
     * @return array
2549
     */
2550
    public function getPermissionsShow($context)
2551
    {
2552
        switch ($context) {
2553
            case self::CONTEXT_DASHBOARD:
2554
            case self::CONTEXT_MENU:
2555
            default:
2556
                return array('LIST');
2557
        }
2558
    }
2559
2560
    /**
2561
     * {@inheritdoc}
2562
     */
2563
    public function showIn($context)
2564
    {
2565
        switch ($context) {
2566
            case self::CONTEXT_DASHBOARD:
2567
            case self::CONTEXT_MENU:
2568
            default:
2569
                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...
2570
        }
2571
    }
2572
2573
    /**
2574
     * {@inheritdoc}
2575
     */
2576
    public function createObjectSecurity($object)
2577
    {
2578
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2579
    }
2580
2581
    /**
2582
     * {@inheritdoc}
2583
     */
2584
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
2585
    {
2586
        $this->securityHandler = $securityHandler;
2587
    }
2588
2589
    /**
2590
     * {@inheritdoc}
2591
     */
2592
    public function getSecurityHandler()
2593
    {
2594
        return $this->securityHandler;
2595
    }
2596
2597
    /**
2598
     * {@inheritdoc}
2599
     */
2600
    public function isGranted($name, $object = null)
2601
    {
2602
        $key = md5(json_encode($name).($object ? '/'.spl_object_hash($object) : ''));
2603
2604
        if (!array_key_exists($key, $this->cacheIsGranted)) {
2605
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2606
        }
2607
2608
        return $this->cacheIsGranted[$key];
2609
    }
2610
2611
    /**
2612
     * {@inheritdoc}
2613
     */
2614
    public function getUrlsafeIdentifier($entity)
2615
    {
2616
        return $this->getModelManager()->getUrlsafeIdentifier($entity);
2617
    }
2618
2619
    /**
2620
     * {@inheritdoc}
2621
     */
2622
    public function getNormalizedIdentifier($entity)
2623
    {
2624
        return $this->getModelManager()->getNormalizedIdentifier($entity);
2625
    }
2626
2627
    /**
2628
     * {@inheritdoc}
2629
     */
2630
    public function id($entity)
2631
    {
2632
        return $this->getNormalizedIdentifier($entity);
2633
    }
2634
2635
    /**
2636
     * {@inheritdoc}
2637
     */
2638
    public function setValidator($validator)
2639
    {
2640
        // TODO: Remove it when bumping requirements to SF 2.5+
2641
        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...
2642
            throw new \InvalidArgumentException(
2643
                'Argument 1 must be an instance of Symfony\Component\Validator\Validator\ValidatorInterface'
2644
                .' or Symfony\Component\Validator\ValidatorInterface'
2645
            );
2646
        }
2647
2648
        $this->validator = $validator;
2649
    }
2650
2651
    /**
2652
     * {@inheritdoc}
2653
     */
2654
    public function getValidator()
2655
    {
2656
        return $this->validator;
2657
    }
2658
2659
    /**
2660
     * {@inheritdoc}
2661
     */
2662
    public function getShow()
2663
    {
2664
        $this->buildShow();
2665
2666
        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...
2667
    }
2668
2669
    /**
2670
     * {@inheritdoc}
2671
     */
2672
    public function setFormTheme(array $formTheme)
2673
    {
2674
        $this->formTheme = $formTheme;
2675
    }
2676
2677
    /**
2678
     * {@inheritdoc}
2679
     */
2680
    public function getFormTheme()
2681
    {
2682
        return $this->formTheme;
2683
    }
2684
2685
    /**
2686
     * {@inheritdoc}
2687
     */
2688
    public function setFilterTheme(array $filterTheme)
2689
    {
2690
        $this->filterTheme = $filterTheme;
2691
    }
2692
2693
    /**
2694
     * {@inheritdoc}
2695
     */
2696
    public function getFilterTheme()
2697
    {
2698
        return $this->filterTheme;
2699
    }
2700
2701
    /**
2702
     * {@inheritdoc}
2703
     */
2704
    public function addExtension(AdminExtensionInterface $extension)
2705
    {
2706
        $this->extensions[] = $extension;
2707
    }
2708
2709
    /**
2710
     * {@inheritdoc}
2711
     */
2712
    public function getExtensions()
2713
    {
2714
        return $this->extensions;
2715
    }
2716
2717
    /**
2718
     * {@inheritdoc}
2719
     */
2720
    public function setMenuFactory(MenuFactoryInterface $menuFactory)
2721
    {
2722
        $this->menuFactory = $menuFactory;
2723
    }
2724
2725
    /**
2726
     * {@inheritdoc}
2727
     */
2728
    public function getMenuFactory()
2729
    {
2730
        return $this->menuFactory;
2731
    }
2732
2733
    /**
2734
     * {@inheritdoc}
2735
     */
2736
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder)
2737
    {
2738
        $this->routeBuilder = $routeBuilder;
2739
    }
2740
2741
    /**
2742
     * {@inheritdoc}
2743
     */
2744
    public function getRouteBuilder()
2745
    {
2746
        return $this->routeBuilder;
2747
    }
2748
2749
    /**
2750
     * {@inheritdoc}
2751
     */
2752
    public function toString($object)
2753
    {
2754
        if (!is_object($object)) {
2755
            return '';
2756
        }
2757
2758
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2759
            return (string) $object;
2760
        }
2761
2762
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2763
    }
2764
2765
    /**
2766
     * {@inheritdoc}
2767
     */
2768
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy)
2769
    {
2770
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2771
    }
2772
2773
    /**
2774
     * {@inheritdoc}
2775
     */
2776
    public function getLabelTranslatorStrategy()
2777
    {
2778
        return $this->labelTranslatorStrategy;
2779
    }
2780
2781
    /**
2782
     * {@inheritdoc}
2783
     */
2784
    public function supportsPreviewMode()
2785
    {
2786
        return $this->supportsPreviewMode;
2787
    }
2788
2789
    /**
2790
     * Set custom per page options.
2791
     *
2792
     * @param array $options
2793
     */
2794
    public function setPerPageOptions(array $options)
2795
    {
2796
        $this->perPageOptions = $options;
2797
    }
2798
2799
    /**
2800
     * Returns predefined per page options.
2801
     *
2802
     * @return array
2803
     */
2804
    public function getPerPageOptions()
2805
    {
2806
        return $this->perPageOptions;
2807
    }
2808
2809
    /**
2810
     * Set pager type.
2811
     *
2812
     * @param string $pagerType
2813
     */
2814
    public function setPagerType($pagerType)
2815
    {
2816
        $this->pagerType = $pagerType;
2817
    }
2818
2819
    /**
2820
     * Get pager type.
2821
     *
2822
     * @return string
2823
     */
2824
    public function getPagerType()
2825
    {
2826
        return $this->pagerType;
2827
    }
2828
2829
    /**
2830
     * Returns true if the per page value is allowed, false otherwise.
2831
     *
2832
     * @param int $perPage
2833
     *
2834
     * @return bool
2835
     */
2836
    public function determinedPerPageValue($perPage)
2837
    {
2838
        return in_array($perPage, $this->perPageOptions);
2839
    }
2840
2841
    /**
2842
     * {@inheritdoc}
2843
     */
2844
    public function isAclEnabled()
2845
    {
2846
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2847
    }
2848
2849
    /**
2850
     * {@inheritdoc}
2851
     */
2852
    public function getObjectMetadata($object)
2853
    {
2854
        return new Metadata($this->toString($object));
2855
    }
2856
2857
    /**
2858
     * {@inheritdoc}
2859
     */
2860
    public function getListModes()
2861
    {
2862
        return $this->listModes;
2863
    }
2864
2865
    /**
2866
     * {@inheritdoc}
2867
     */
2868
    public function setListMode($mode)
2869
    {
2870
        if (!$this->hasRequest()) {
2871
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2872
        }
2873
2874
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2875
    }
2876
2877
    /**
2878
     * {@inheritdoc}
2879
     */
2880
    public function getListMode()
2881
    {
2882
        if (!$this->hasRequest()) {
2883
            return 'list';
2884
        }
2885
2886
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2887
    }
2888
2889
    /**
2890
     * {@inheritdoc}
2891
     */
2892
    public function getAccessMapping()
2893
    {
2894
        return $this->accessMapping;
2895
    }
2896
2897
    /**
2898
     * {@inheritdoc}
2899
     */
2900
    public function checkAccess($action, $object = null)
2901
    {
2902
        $access = $this->getAccess();
2903
2904
        if (!array_key_exists($action, $access)) {
2905
            throw new \InvalidArgumentException(sprintf(
2906
                'Action "%s" could not be found in access mapping.'
2907
                .' Please make sure your action is defined into your admin class accessMapping property.',
2908
                $action
2909
            ));
2910
        }
2911
2912
        if (!is_array($access[$action])) {
2913
            $access[$action] = array($access[$action]);
2914
        }
2915
2916
        foreach ($access[$action] as $role) {
2917
            if (false === $this->isGranted($role, $object)) {
2918
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2919
            }
2920
        }
2921
    }
2922
2923
    /**
2924
     * Hook to handle access authorization, without throw Exception.
2925
     *
2926
     * @param string $action
2927
     * @param object $object
2928
     *
2929
     * @return bool
2930
     */
2931
    public function hasAccess($action, $object = null)
2932
    {
2933
        $access = $this->getAccess();
2934
2935
        if (!array_key_exists($action, $access)) {
2936
            return false;
2937
        }
2938
2939
        if (!is_array($access[$action])) {
2940
            $access[$action] = array($access[$action]);
2941
        }
2942
2943
        foreach ($access[$action] as $role) {
2944
            if (false === $this->isGranted($role, $object)) {
2945
                return false;
2946
            }
2947
        }
2948
2949
        return true;
2950
    }
2951
2952
    /**
2953
     * {@inheritdoc}
2954
     */
2955
    public function configureActionButtons($action, $object = null)
2956
    {
2957
        $list = array();
2958
2959
        if (in_array($action, array('tree', 'show', 'edit', 'delete', 'list', 'batch'))
2960
            && $this->hasAccess('create')
2961
            && $this->hasRoute('create')
2962
        ) {
2963
            $list['create'] = array(
2964
                'template' => $this->getTemplate('button_create'),
2965
            );
2966
        }
2967
2968
        if (in_array($action, array('show', 'delete', 'acl', 'history'))
2969
            && $this->canAccessObject('edit', $object)
2970
            && $this->hasRoute('edit')
2971
        ) {
2972
            $list['edit'] = array(
2973
                'template' => $this->getTemplate('button_edit'),
2974
            );
2975
        }
2976
2977
        if (in_array($action, array('show', 'edit', 'acl'))
2978
            && $this->canAccessObject('history', $object)
2979
            && $this->hasRoute('history')
2980
        ) {
2981
            $list['history'] = array(
2982
                'template' => $this->getTemplate('button_history'),
2983
            );
2984
        }
2985
2986
        if (in_array($action, array('edit', 'history'))
2987
            && $this->isAclEnabled()
2988
            && $this->canAccessObject('acl', $object)
2989
            && $this->hasRoute('acl')
2990
        ) {
2991
            $list['acl'] = array(
2992
                'template' => $this->getTemplate('button_acl'),
2993
            );
2994
        }
2995
2996
        if (in_array($action, array('edit', 'history', 'acl'))
2997
            && $this->canAccessObject('show', $object)
2998
            && count($this->getShow()) > 0
2999
            && $this->hasRoute('show')
3000
        ) {
3001
            $list['show'] = array(
3002
                'template' => $this->getTemplate('button_show'),
3003
            );
3004
        }
3005
3006
        if (in_array($action, array('show', 'edit', 'delete', 'acl', 'batch'))
3007
            && $this->hasAccess('list')
3008
            && $this->hasRoute('list')
3009
        ) {
3010
            $list['list'] = array(
3011
                'template' => $this->getTemplate('button_list'),
3012
            );
3013
        }
3014
3015
        return $list;
3016
    }
3017
3018
    /**
3019
     * @param string $action
3020
     * @param mixed  $object
3021
     *
3022
     * @return array
3023
     */
3024
    public function getActionButtons($action, $object = null)
3025
    {
3026
        $list = $this->configureActionButtons($action, $object);
3027
3028
        foreach ($this->getExtensions() as $extension) {
3029
            // TODO: remove method check in next major release
3030
            if (method_exists($extension, 'configureActionButtons')) {
3031
                $list = $extension->configureActionButtons($this, $list, $action, $object);
3032
            }
3033
        }
3034
3035
        return $list;
3036
    }
3037
3038
    /**
3039
     * Get the list of actions that can be accessed directly from the dashboard.
3040
     *
3041
     * @return array
3042
     */
3043
    public function getDashboardActions()
3044
    {
3045
        $actions = array();
3046
3047
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
3048
            $actions['create'] = array(
3049
                'label' => 'link_add',
3050
                'translation_domain' => 'SonataAdminBundle',
3051
                'template' => $this->getTemplate('action_create'),
3052
                'url' => $this->generateUrl('create'),
3053
                'icon' => 'plus-circle',
3054
            );
3055
        }
3056
3057
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
3058
            $actions['list'] = array(
3059
                'label' => 'link_list',
3060
                'translation_domain' => 'SonataAdminBundle',
3061
                'url' => $this->generateUrl('list'),
3062
                'icon' => 'list',
3063
            );
3064
        }
3065
3066
        return $actions;
3067
    }
3068
3069
    /**
3070
     * Setting to true will enable mosaic button for the admin screen.
3071
     * Setting to false will hide mosaic button for the admin screen.
3072
     *
3073
     * @param bool $isShown
3074
     */
3075
    final public function showMosaicButton($isShown)
3076
    {
3077
        if ($isShown) {
3078
            $this->listModes['mosaic'] = array('class' => self::MOSAIC_ICON_CLASS);
3079
        } else {
3080
            unset($this->listModes['mosaic']);
3081
        }
3082
    }
3083
3084
    /**
3085
     * @param FormMapper $form
0 ignored issues
show
Bug introduced by
There is no parameter named $form. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
3086
     */
3087
    final public function getSearchResultLink($object)
3088
    {
3089
        foreach ($this->searchResultActions as $action) {
3090
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
3091
                return $this->generateObjectUrl($action, $object);
3092
            }
3093
        }
3094
3095
        return;
3096
    }
3097
3098
    /**
3099
     * Checks if a filter type is set to a default value.
3100
     *
3101
     * @param string $name
3102
     *
3103
     * @return bool
3104
     */
3105
    final public function isDefaultFilter($name)
3106
    {
3107
        $filter = $this->getFilterParameters();
3108
        $default = $this->getDefaultFilterValues();
3109
3110
        if (!array_key_exists($name, $filter) || !array_key_exists($name, $default)) {
3111
            return false;
3112
        }
3113
3114
        return $filter[$name] == $default[$name];
3115
    }
3116
3117
    /**
3118
     * Check object existence and access, without throw Exception.
3119
     *
3120
     * @param string $action
3121
     * @param object $object
3122
     *
3123
     * @return bool
3124
     */
3125
    public function canAccessObject($action, $object)
3126
    {
3127
        return $object && $this->id($object) && $this->hasAccess($action, $object);
3128
    }
3129
3130
    /**
3131
     * Returns a list of default filters.
3132
     *
3133
     * @return array
3134
     */
3135
    final protected function getDefaultFilterValues()
3136
    {
3137
        $defaultFilterValues = array();
3138
3139
        $this->configureDefaultFilterValues($defaultFilterValues);
3140
3141
        foreach ($this->getExtensions() as $extension) {
3142
            // NEXT_MAJOR: remove method check in next major release
3143
            if (method_exists($extension, 'configureDefaultFilterValues')) {
3144
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
3145
            }
3146
        }
3147
3148
        return $defaultFilterValues;
3149
    }
3150
3151
    /**
3152
     * {@inheritdoc}
3153
     */
3154
    protected function configureFormFields(FormMapper $form)
3155
    {
3156
    }
3157
3158
    /**
3159
     * @param ListMapper $list
3160
     */
3161
    protected function configureListFields(ListMapper $list)
3162
    {
3163
    }
3164
3165
    /**
3166
     * @param DatagridMapper $filter
3167
     */
3168
    protected function configureDatagridFilters(DatagridMapper $filter)
3169
    {
3170
    }
3171
3172
    /**
3173
     * @param ShowMapper $show
3174
     */
3175
    protected function configureShowFields(ShowMapper $show)
3176
    {
3177
    }
3178
3179
    /**
3180
     * @param RouteCollection $collection
3181
     */
3182
    protected function configureRoutes(RouteCollection $collection)
3183
    {
3184
    }
3185
3186
    /**
3187
     * Allows you to customize batch actions.
3188
     *
3189
     * @param array $actions List of actions
3190
     *
3191
     * @return array
3192
     */
3193
    protected function configureBatchActions($actions)
3194
    {
3195
        return $actions;
3196
    }
3197
3198
    /**
3199
     * NEXT_MAJOR: remove this method.
3200
     *
3201
     * @param MenuItemInterface $menu
3202
     * @param                   $action
3203
     * @param AdminInterface    $childAdmin
3204
     *
3205
     * @return mixed
3206
     *
3207
     * @deprecated Use configureTabMenu instead
3208
     */
3209
    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...
3210
    {
3211
    }
3212
3213
    /**
3214
     * Configures the tab menu in your admin.
3215
     *
3216
     * @param MenuItemInterface $menu
3217
     * @param string            $action
3218
     * @param AdminInterface    $childAdmin
3219
     *
3220
     * @return mixed
3221
     */
3222
    protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
3223
    {
3224
        // Use configureSideMenu not to mess with previous overrides
3225
        // TODO remove once deprecation period is over
3226
        $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...
3227
    }
3228
3229
    /**
3230
     * build the view FieldDescription array.
3231
     */
3232
    protected function buildShow()
3233
    {
3234
        if ($this->show) {
3235
            return;
3236
        }
3237
3238
        $this->show = new FieldDescriptionCollection();
3239
        $mapper = new ShowMapper($this->showBuilder, $this->show, $this);
3240
3241
        $this->configureShowFields($mapper);
3242
3243
        foreach ($this->getExtensions() as $extension) {
3244
            $extension->configureShowFields($mapper);
3245
        }
3246
    }
3247
3248
    /**
3249
     * build the list FieldDescription array.
3250
     */
3251
    protected function buildList()
3252
    {
3253
        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...
3254
            return;
3255
        }
3256
3257
        $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...
3258
3259
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
3260
3261
        if (count($this->getBatchActions()) > 0) {
3262
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3263
                $this->getClass(),
3264
                'batch',
3265
                array(
3266
                    'label' => 'batch',
3267
                    'code' => '_batch',
3268
                    'sortable' => false,
3269
                    'virtual_field' => true,
3270
                )
3271
            );
3272
3273
            $fieldDescription->setAdmin($this);
3274
            $fieldDescription->setTemplate($this->getTemplate('batch'));
3275
3276
            $mapper->add($fieldDescription, 'batch');
3277
        }
3278
3279
        $this->configureListFields($mapper);
3280
3281
        foreach ($this->getExtensions() as $extension) {
3282
            $extension->configureListFields($mapper);
3283
        }
3284
3285
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
3286
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3287
                $this->getClass(),
3288
                'select',
3289
                array(
3290
                    'label' => false,
3291
                    'code' => '_select',
3292
                    'sortable' => false,
3293
                    'virtual_field' => false,
3294
                )
3295
            );
3296
3297
            $fieldDescription->setAdmin($this);
3298
            $fieldDescription->setTemplate($this->getTemplate('select'));
3299
3300
            $mapper->add($fieldDescription, 'select');
3301
        }
3302
    }
3303
3304
    /**
3305
     * Build the form FieldDescription collection.
3306
     */
3307
    protected function buildForm()
3308
    {
3309
        if ($this->form) {
3310
            return;
3311
        }
3312
3313
        // append parent object if any
3314
        // todo : clean the way the Admin class can retrieve set the object
3315
        if ($this->isChild() && $this->getParentAssociationMapping()) {
3316
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
3317
3318
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3319
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3320
3321
            $object = $this->getSubject();
3322
3323
            $value = $propertyAccessor->getValue($object, $propertyPath);
3324
3325
            if (is_array($value) || ($value instanceof \Traversable && $value instanceof \ArrayAccess)) {
3326
                $value[] = $parent;
3327
                $propertyAccessor->setValue($object, $propertyPath, $value);
3328
            } else {
3329
                $propertyAccessor->setValue($object, $propertyPath, $parent);
3330
            }
3331
        }
3332
3333
        $this->form = $this->getFormBuilder()->getForm();
3334
    }
3335
3336
    /**
3337
     * Gets the subclass corresponding to the given name.
3338
     *
3339
     * @param string $name The name of the sub class
3340
     *
3341
     * @return string the subclass
3342
     */
3343
    protected function getSubClass($name)
3344
    {
3345
        if ($this->hasSubClass($name)) {
3346
            return $this->subClasses[$name];
3347
        }
3348
3349
        throw new \RuntimeException(sprintf(
3350
            'Unable to find the subclass `%s` for admin `%s`',
3351
            $name,
3352
            get_class($this)
3353
        ));
3354
    }
3355
3356
    /**
3357
     * Attach the inline validator to the model metadata, this must be done once per admin.
3358
     */
3359
    protected function attachInlineValidator()
3360
    {
3361
        $admin = $this;
3362
3363
        // add the custom inline validation option
3364
        // TODO: Remove conditional method when bumping requirements to SF 2.5+
3365
        if (method_exists($this->validator, 'getMetadataFor')) {
3366
            $metadata = $this->validator->getMetadataFor($this->getClass());
3367
        } else {
3368
            $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...
3369
        }
3370
3371
        $metadata->addConstraint(new InlineConstraint(array(
3372
            'service' => $this,
3373
            'method' => function (ErrorElement $errorElement, $object) use ($admin) {
3374
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
3375
3376
                // This avoid the main validation to be cascaded to children
3377
                // The problem occurs when a model Page has a collection of Page as property
3378
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
3379
                    return;
3380
                }
3381
3382
                $admin->validate($errorElement, $object);
3383
3384
                foreach ($admin->getExtensions() as $extension) {
3385
                    $extension->validate($admin, $errorElement, $object);
3386
                }
3387
            },
3388
            'serializingWarning' => true,
3389
        )));
3390
    }
3391
3392
    /**
3393
     * Predefine per page options.
3394
     */
3395
    protected function predefinePerPageOptions()
3396
    {
3397
        array_unshift($this->perPageOptions, $this->maxPerPage);
3398
        $this->perPageOptions = array_unique($this->perPageOptions);
3399
        sort($this->perPageOptions);
3400
    }
3401
3402
    /**
3403
     * Return list routes with permissions name.
3404
     *
3405
     * @return array
3406
     */
3407
    protected function getAccess()
3408
    {
3409
        $access = array_merge(array(
3410
            'acl' => 'MASTER',
3411
            'export' => 'EXPORT',
3412
            'historyCompareRevisions' => 'EDIT',
3413
            'historyViewRevision' => 'EDIT',
3414
            'history' => 'EDIT',
3415
            'edit' => 'EDIT',
3416
            'show' => 'VIEW',
3417
            'create' => 'CREATE',
3418
            'delete' => 'DELETE',
3419
            'batchDelete' => 'DELETE',
3420
            'list' => 'LIST',
3421
        ), $this->getAccessMapping());
3422
3423
        foreach ($this->extensions as $extension) {
3424
            // TODO: remove method check in next major release
3425
            if (method_exists($extension, 'getAccessMapping')) {
3426
                $access = array_merge($access, $extension->getAccessMapping($this));
3427
            }
3428
        }
3429
3430
        return $access;
3431
    }
3432
3433
    /**
3434
     * Returns a list of default filters.
3435
     *
3436
     * @param array $filterValues
3437
     */
3438
    protected function configureDefaultFilterValues(array &$filterValues)
3439
    {
3440
    }
3441
3442
    /**
3443
     * Build all the related urls to the current admin.
3444
     */
3445
    private function buildRoutes()
3446
    {
3447
        if ($this->loaded['routes']) {
3448
            return;
3449
        }
3450
3451
        $this->loaded['routes'] = true;
3452
3453
        $this->routes = new RouteCollection(
3454
            $this->getBaseCodeRoute(),
3455
            $this->getBaseRouteName(),
3456
            $this->getBaseRoutePattern(),
3457
            $this->getBaseControllerName()
3458
        );
3459
3460
        $this->routeBuilder->build($this, $this->routes);
3461
3462
        $this->configureRoutes($this->routes);
3463
3464
        foreach ($this->getExtensions() as $extension) {
3465
            $extension->configureRoutes($this, $this->routes);
3466
        }
3467
    }
3468
}
3469