Completed
Pull Request — 3.x (#4636)
by Chris
04:28
created

AbstractAdmin::iDoNothing()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/*
4
 * This file is part of the Sonata Project package.
5
 *
6
 * (c) Thomas Rabaix <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sonata\AdminBundle\Admin;
13
14
use Doctrine\Common\Util\ClassUtils;
15
use Knp\Menu\FactoryInterface as MenuFactoryInterface;
16
use Knp\Menu\ItemInterface;
17
use Knp\Menu\ItemInterface as MenuItemInterface;
18
use Sonata\AdminBundle\Builder\DatagridBuilderInterface;
19
use Sonata\AdminBundle\Builder\FormContractorInterface;
20
use Sonata\AdminBundle\Builder\ListBuilderInterface;
21
use Sonata\AdminBundle\Builder\RouteBuilderInterface;
22
use Sonata\AdminBundle\Builder\ShowBuilderInterface;
23
use Sonata\AdminBundle\Datagrid\DatagridInterface;
24
use Sonata\AdminBundle\Datagrid\DatagridMapper;
25
use Sonata\AdminBundle\Datagrid\ListMapper;
26
use Sonata\AdminBundle\Datagrid\Pager;
27
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
28
use Sonata\AdminBundle\Form\FormMapper;
29
use Sonata\AdminBundle\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;
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
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
     * @var string
231
     */
232
    protected $baseCodeRoute = '';
233
234
    /**
235
     * The related parent association, ie if OrderElement has a parent property named order,
236
     * then the $parentAssociationMapping must be a string named `order`.
237
     *
238
     * @var string
239
     */
240
    protected $parentAssociationMapping = null;
241
242
    /**
243
     * Reference the parent FieldDescription related to this admin
244
     * only set for FieldDescription which is associated to an Sub Admin instance.
245
     *
246
     * @var FieldDescriptionInterface
247
     */
248
    protected $parentFieldDescription;
249
250
    /**
251
     * If true then the current admin is part of the nested admin set (from the url).
252
     *
253
     * @var bool
254
     */
255
    protected $currentChild = false;
256
257
    /**
258
     * The uniqid is used to avoid clashing with 2 admin related to the code
259
     * ie: a Block linked to a Block.
260
     *
261
     * @var string
262
     */
263
    protected $uniqid;
264
265
    /**
266
     * The Entity or Document manager.
267
     *
268
     * @var ModelManagerInterface
269
     */
270
    protected $modelManager;
271
272
    /**
273
     * The current request object.
274
     *
275
     * @var \Symfony\Component\HttpFoundation\Request
276
     */
277
    protected $request;
278
279
    /**
280
     * The translator component.
281
     *
282
     * NEXT_MAJOR: remove this property
283
     *
284
     * @var \Symfony\Component\Translation\TranslatorInterface
285
     *
286
     * @deprecated since 3.9, to be removed with 4.0
287
     */
288
    protected $translator;
289
290
    /**
291
     * The related form contractor.
292
     *
293
     * @var FormContractorInterface
294
     */
295
    protected $formContractor;
296
297
    /**
298
     * The related list builder.
299
     *
300
     * @var ListBuilderInterface
301
     */
302
    protected $listBuilder;
303
304
    /**
305
     * The related view builder.
306
     *
307
     * @var ShowBuilderInterface
308
     */
309
    protected $showBuilder;
310
311
    /**
312
     * The related datagrid builder.
313
     *
314
     * @var DatagridBuilderInterface
315
     */
316
    protected $datagridBuilder;
317
318
    /**
319
     * @var RouteBuilderInterface
320
     */
321
    protected $routeBuilder;
322
323
    /**
324
     * The datagrid instance.
325
     *
326
     * @var \Sonata\AdminBundle\Datagrid\DatagridInterface
327
     */
328
    protected $datagrid;
329
330
    /**
331
     * The router instance.
332
     *
333
     * @var RouteGeneratorInterface
334
     */
335
    protected $routeGenerator;
336
337
    /**
338
     * The generated breadcrumbs.
339
     *
340
     * NEXT_MAJOR : remove this property
341
     *
342
     * @var array
343
     */
344
    protected $breadcrumbs = array();
345
346
    /**
347
     * @var SecurityHandlerInterface
348
     */
349
    protected $securityHandler = null;
350
351
    /**
352
     * @var ValidatorInterface|LegacyValidatorInterface
353
     */
354
    protected $validator = null;
355
356
    /**
357
     * The configuration pool.
358
     *
359
     * @var Pool
360
     */
361
    protected $configurationPool;
362
363
    /**
364
     * @var MenuItemInterface
365
     */
366
    protected $menu;
367
368
    /**
369
     * @var MenuFactoryInterface
370
     */
371
    protected $menuFactory;
372
373
    /**
374
     * @var array
375
     */
376
    protected $loaded = array(
377
        'view_fields' => false,
378
        'view_groups' => false,
379
        'routes' => false,
380
        'tab_menu' => false,
381
    );
382
383
    /**
384
     * @var array
385
     */
386
    protected $formTheme = array();
387
388
    /**
389
     * @var array
390
     */
391
    protected $filterTheme = array();
392
393
    /**
394
     * @var array
395
     */
396
    protected $templates = array();
397
398
    /**
399
     * @var AdminExtensionInterface[]
400
     */
401
    protected $extensions = array();
402
403
    /**
404
     * @var LabelTranslatorStrategyInterface
405
     */
406
    protected $labelTranslatorStrategy;
407
408
    /**
409
     * Setting to true will enable preview mode for
410
     * the entity and show a preview button in the
411
     * edit/create forms.
412
     *
413
     * @var bool
414
     */
415
    protected $supportsPreviewMode = false;
416
417
    /**
418
     * Roles and permissions per role.
419
     *
420
     * @var array [role] => array([permission], [permission])
421
     */
422
    protected $securityInformation = array();
423
424
    protected $cacheIsGranted = array();
425
426
    /**
427
     * Action list for the search result.
428
     *
429
     * @var string[]
430
     */
431
    protected $searchResultActions = array('edit', 'show');
432
433
    protected $listModes = array(
434
        'list' => array(
435
            'class' => 'fa fa-list fa-fw',
436
        ),
437
        'mosaic' => array(
438
            'class' => self::MOSAIC_ICON_CLASS,
439
        ),
440
    );
441
442
    /**
443
     * The Access mapping.
444
     *
445
     * @var array [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
446
     */
447
    protected $accessMapping = array();
448
449
    /**
450
     * The class name managed by the admin class.
451
     *
452
     * @var string
453
     */
454
    private $class;
455
456
    /**
457
     * The subclasses supported by the admin class.
458
     *
459
     * @var array
460
     */
461
    private $subClasses = array();
462
463
    /**
464
     * The list collection.
465
     *
466
     * @var array
467
     */
468
    private $list;
469
470
    /**
471
     * @var FieldDescriptionCollection
472
     */
473
    private $show;
474
475
    /**
476
     * @var Form
477
     */
478
    private $form;
479
480
    /**
481
     * @var DatagridInterface
482
     */
483
    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...
484
485
    /**
486
     * The cached base route name.
487
     *
488
     * @var string
489
     */
490
    private $cachedBaseRouteName;
491
492
    /**
493
     * The cached base route pattern.
494
     *
495
     * @var string
496
     */
497
    private $cachedBaseRoutePattern;
498
499
    /**
500
     * The form group disposition.
501
     *
502
     * @var array|bool
503
     */
504
    private $formGroups = false;
505
506
    /**
507
     * The form tabs disposition.
508
     *
509
     * @var array|bool
510
     */
511
    private $formTabs = false;
512
513
    /**
514
     * The view group disposition.
515
     *
516
     * @var array|bool
517
     */
518
    private $showGroups = false;
519
520
    /**
521
     * The view tab disposition.
522
     *
523
     * @var array|bool
524
     */
525
    private $showTabs = false;
526
527
    /**
528
     * The manager type to use for the admin.
529
     *
530
     * @var string
531
     */
532
    private $managerType;
533
534
    /**
535
     * The breadcrumbsBuilder component.
536
     *
537
     * @var BreadcrumbsBuilderInterface
538
     */
539
    private $breadcrumbsBuilder;
540
541
    /**
542
     * @param string $code
543
     * @param string $class
544
     * @param string $baseControllerName
545
     */
546
    public function __construct($code, $class, $baseControllerName)
547
    {
548
        $this->code = $code;
549
        $this->class = $class;
550
        $this->baseControllerName = $baseControllerName;
551
552
        $this->predefinePerPageOptions();
553
        $this->datagridValues['_per_page'] = $this->maxPerPage;
554
    }
555
556
    /**
557
     * {@inheritdoc}
558
     *
559
     * NEXT_MAJOR: return null to indicate no override
560
     */
561
    public function getExportFormats()
562
    {
563
        return array(
564
            'json', 'xml', 'csv', 'xls',
565
        );
566
    }
567
568
    /**
569
     * @return array
570
     */
571
    public function getExportFields()
572
    {
573
        $fields = $this->getModelManager()->getExportFields($this->getClass());
574
575
        foreach ($this->getExtensions() as $extension) {
576
            if (method_exists($extension, 'configureExportFields')) {
577
                $fields = $extension->configureExportFields($this, $fields);
578
            }
579
        }
580
581
        return $fields;
582
    }
583
584
    /**
585
     * {@inheritdoc}
586
     */
587
    public function getDataSourceIterator()
588
    {
589
        $datagrid = $this->getDatagrid();
590
        $datagrid->buildPager();
591
592
        $fields = array();
593
594
        foreach ($this->getExportFields() as $key => $field) {
595
            $label = $this->getTranslationLabel($field, 'export', 'label');
596
            $transLabel = $this->trans($label);
597
598
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
599
            // No translation key exists
600
            if ($transLabel == $label) {
601
                $fields[$key] = $field;
602
            } else {
603
                $fields[$transLabel] = $field;
604
            }
605
        }
606
607
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
608
    }
609
610
    /**
611
     * {@inheritdoc}
612
     */
613
    public function validate(ErrorElement $errorElement, $object)
614
    {
615
    }
616
617
    /**
618
     * define custom variable.
619
     */
620
    public function initialize()
621
    {
622
        if (!$this->classnameLabel) {
623
            $this->classnameLabel = substr($this->getClass(), strrpos($this->getClass(), '\\') + 1);
624
        }
625
626
        $this->baseCodeRoute = $this->getCode();
627
628
        $this->configure();
629
    }
630
631
    /**
632
     * {@inheritdoc}
633
     */
634
    public function configure()
635
    {
636
    }
637
638
    /**
639
     * {@inheritdoc}
640
     */
641
    public function update($object)
642
    {
643
        $this->preUpdate($object);
644
        foreach ($this->extensions as $extension) {
645
            $extension->preUpdate($this, $object);
646
        }
647
648
        $result = $this->getModelManager()->update($object);
649
        // BC compatibility
650
        if (null !== $result) {
651
            $object = $result;
652
        }
653
654
        $this->postUpdate($object);
655
        foreach ($this->extensions as $extension) {
656
            $extension->postUpdate($this, $object);
657
        }
658
659
        return $object;
660
    }
661
662
    /**
663
     * {@inheritdoc}
664
     */
665
    public function create($object)
666
    {
667
        $this->prePersist($object);
668
        foreach ($this->extensions as $extension) {
669
            $extension->prePersist($this, $object);
670
        }
671
672
        $result = $this->getModelManager()->create($object);
673
        // BC compatibility
674
        if (null !== $result) {
675
            $object = $result;
676
        }
677
678
        $this->postPersist($object);
679
        foreach ($this->extensions as $extension) {
680
            $extension->postPersist($this, $object);
681
        }
682
683
        $this->createObjectSecurity($object);
684
685
        return $object;
686
    }
687
688
    /**
689
     * {@inheritdoc}
690
     */
691
    public function delete($object)
692
    {
693
        $this->preRemove($object);
694
        foreach ($this->extensions as $extension) {
695
            $extension->preRemove($this, $object);
696
        }
697
698
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
699
        $this->getModelManager()->delete($object);
700
701
        $this->postRemove($object);
702
        foreach ($this->extensions as $extension) {
703
            $extension->postRemove($this, $object);
704
        }
705
    }
706
707
    /**
708
     * {@inheritdoc}
709
     */
710
    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...
711
    {
712
    }
713
714
    /**
715
     * {@inheritdoc}
716
     */
717
    public function preUpdate($object)
718
    {
719
    }
720
721
    /**
722
     * {@inheritdoc}
723
     */
724
    public function postUpdate($object)
725
    {
726
    }
727
728
    /**
729
     * {@inheritdoc}
730
     */
731
    public function prePersist($object)
732
    {
733
    }
734
735
    /**
736
     * {@inheritdoc}
737
     */
738
    public function postPersist($object)
739
    {
740
    }
741
742
    /**
743
     * {@inheritdoc}
744
     */
745
    public function preRemove($object)
746
    {
747
    }
748
749
    /**
750
     * {@inheritdoc}
751
     */
752
    public function postRemove($object)
753
    {
754
    }
755
756
    /**
757
     * {@inheritdoc}
758
     */
759
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
760
    {
761
    }
762
763
    /**
764
     * {@inheritdoc}
765
     */
766
    public function getFilterParameters()
767
    {
768
        $parameters = array();
769
770
        // build the values array
771
        if ($this->hasRequest()) {
772
            $filters = $this->request->query->get('filter', array());
773
774
            // if persisting filters, save filters to session, or pull them out of session if no new filters set
775
            if ($this->persistFilters) {
776
                if ($filters == array() && $this->request->query->get('filters') != 'reset') {
777
                    $filters = $this->request->getSession()->get($this->getCode().'.filter.parameters', array());
778
                } else {
779
                    $this->request->getSession()->set($this->getCode().'.filter.parameters', $filters);
780
                }
781
            }
782
783
            $parameters = array_merge(
784
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
785
                $this->datagridValues,
786
                $this->getDefaultFilterValues(),
787
                $filters
788
            );
789
790
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
791
                $parameters['_per_page'] = $this->maxPerPage;
792
            }
793
794
            // always force the parent value
795
            if ($this->isChild() && $this->getParentAssociationMapping()) {
796
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
797
                $parameters[$name] = array('value' => $this->request->get($this->getParent()->getIdParameter()));
798
            }
799
        }
800
801
        return $parameters;
802
    }
803
804
    /**
805
     * {@inheritdoc}
806
     */
807
    public function buildDatagrid()
808
    {
809
        if ($this->datagrid) {
810
            return;
811
        }
812
813
        $filterParameters = $this->getFilterParameters();
814
815
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
816
        if (isset($filterParameters['_sort_by']) && is_string($filterParameters['_sort_by'])) {
817
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
818
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
819
            } else {
820
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
821
                    $this->getClass(),
822
                    $filterParameters['_sort_by'],
823
                    array()
824
                );
825
826
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
827
            }
828
        }
829
830
        // initialize the datagrid
831
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
832
833
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
834
835
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
836
837
        // build the datagrid filter
838
        $this->configureDatagridFilters($mapper);
839
840
        // ok, try to limit to add parent filter
841
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
842
            // NEXT_MAJOR: Keep FQCN when bumping Symfony requirement to 2.8+.
843
            $modelHiddenType = method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
844
                ? 'Sonata\AdminBundle\Form\Type\ModelHiddenType'
845
                : 'sonata_type_model_hidden';
846
847
            // NEXT_MAJOR: Keep FQCN when bumping Symfony requirement to 2.8+.
848
            $hiddenType = method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
849
                ? 'Symfony\Component\Form\Extension\Core\Type\HiddenType'
850
                : 'hidden';
851
852
            $mapper->add($this->getParentAssociationMapping(), null, array(
853
                'show_filter' => false,
854
                'label' => false,
855
                'field_type' => $modelHiddenType,
856
                'field_options' => array(
857
                    'model_manager' => $this->getModelManager(),
858
                ),
859
                'operator_type' => $hiddenType,
860
            ), null, null, array(
861
                'admin_code' => $this->getParent()->getCode(),
862
            ));
863
        }
864
865
        foreach ($this->getExtensions() as $extension) {
866
            $extension->configureDatagridFilters($mapper);
867
        }
868
    }
869
870
    /**
871
     * Returns the name of the parent related field, so the field can be use to set the default
872
     * value (ie the parent object) or to filter the object.
873
     *
874
     * @return string the name of the parent related field
875
     */
876
    public function getParentAssociationMapping()
877
    {
878
        return $this->parentAssociationMapping;
879
    }
880
881
    /**
882
     * Returns the baseRoutePattern used to generate the routing information.
883
     *
884
     * @throws \RuntimeException
885
     *
886
     * @return string the baseRoutePattern used to generate the routing information
887
     */
888
    public function getBaseRoutePattern()
889
    {
890
        if (null !== $this->cachedBaseRoutePattern) {
891
            return $this->cachedBaseRoutePattern;
892
        }
893
894
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
895
            if (!$this->baseRoutePattern) {
896
                preg_match(self::CLASS_REGEX, $this->class, $matches);
897
898
                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...
899
                    throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
900
                }
901
            }
902
903
            $this->cachedBaseRoutePattern = sprintf('%s/{id}/%s',
904
                $this->getParent()->getBaseRoutePattern(),
905
                $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...
906
            );
907
        } elseif ($this->baseRoutePattern) {
908
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
909
        } else {
910
            preg_match(self::CLASS_REGEX, $this->class, $matches);
911
912
            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...
913
                throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
914
            }
915
916
            $this->cachedBaseRoutePattern = sprintf('/%s%s/%s',
917
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
918
                $this->urlize($matches[3], '-'),
919
                $this->urlize($matches[5], '-')
920
            );
921
        }
922
923
        return $this->cachedBaseRoutePattern;
924
    }
925
926
    /**
927
     * Returns the baseRouteName used to generate the routing information.
928
     *
929
     * @throws \RuntimeException
930
     *
931
     * @return string the baseRouteName used to generate the routing information
932
     */
933
    public function getBaseRouteName()
934
    {
935
        if (null !== $this->cachedBaseRouteName) {
936
            return $this->cachedBaseRouteName;
937
        }
938
939
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
940
            if (!$this->baseRouteName) {
941
                preg_match(self::CLASS_REGEX, $this->class, $matches);
942
943
                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...
944
                    throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
945
                }
946
            }
947
948
            $this->cachedBaseRouteName = sprintf('%s_%s',
949
                $this->getParent()->getBaseRouteName(),
950
                $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...
951
            );
952
        } elseif ($this->baseRouteName) {
953
            $this->cachedBaseRouteName = $this->baseRouteName;
954
        } else {
955
            preg_match(self::CLASS_REGEX, $this->class, $matches);
956
957
            if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
958
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
959
            }
960
961
            $this->cachedBaseRouteName = sprintf('admin_%s%s_%s',
962
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
963
                $this->urlize($matches[3]),
964
                $this->urlize($matches[5])
965
            );
966
        }
967
968
        return $this->cachedBaseRouteName;
969
    }
970
971
    /**
972
     * urlize the given word.
973
     *
974
     * @param string $word
975
     * @param string $sep  the separator
976
     *
977
     * @return string
978
     */
979
    public function urlize($word, $sep = '_')
980
    {
981
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
982
    }
983
984
    /**
985
     * {@inheritdoc}
986
     */
987
    public function getClass()
988
    {
989
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
990
        if ($this->hasSubject() && is_object($this->getSubject())) {
991
            return ClassUtils::getClass($this->getSubject());
992
        }
993
994
        if (!$this->hasActiveSubClass()) {
995
            if (count($this->getSubClasses()) > 0) {
996
                $subject = $this->getSubject();
997
998
                if ($subject && is_object($subject)) {
999
                    return ClassUtils::getClass($subject);
1000
                }
1001
            }
1002
1003
            return $this->class;
1004
        }
1005
1006
        if ($this->getParentFieldDescription() && $this->hasActiveSubClass()) {
1007
            throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
1008
        }
1009
1010
        $subClass = $this->getRequest()->query->get('subclass');
1011
1012
        return $this->getSubClass($subClass);
1013
    }
1014
1015
    /**
1016
     * {@inheritdoc}
1017
     */
1018
    public function getSubClasses()
1019
    {
1020
        return $this->subClasses;
1021
    }
1022
1023
    /**
1024
     * {@inheritdoc}
1025
     */
1026
    public function addSubClass($subClass)
1027
    {
1028
        if (!in_array($subClass, $this->subClasses)) {
1029
            $this->subClasses[] = $subClass;
1030
        }
1031
    }
1032
1033
    /**
1034
     * {@inheritdoc}
1035
     */
1036
    public function setSubClasses(array $subClasses)
1037
    {
1038
        $this->subClasses = $subClasses;
1039
    }
1040
1041
    /**
1042
     * {@inheritdoc}
1043
     */
1044
    public function hasSubClass($name)
1045
    {
1046
        return isset($this->subClasses[$name]);
1047
    }
1048
1049
    /**
1050
     * {@inheritdoc}
1051
     */
1052
    public function hasActiveSubClass()
1053
    {
1054
        if (count($this->subClasses) > 0 && $this->request) {
1055
            return null !== $this->getRequest()->query->get('subclass');
1056
        }
1057
1058
        return false;
1059
    }
1060
1061
    /**
1062
     * {@inheritdoc}
1063
     */
1064
    public function getActiveSubClass()
1065
    {
1066
        if (!$this->hasActiveSubClass()) {
1067
            return;
1068
        }
1069
1070
        return $this->getClass();
1071
    }
1072
1073
    /**
1074
     * {@inheritdoc}
1075
     */
1076
    public function getActiveSubclassCode()
1077
    {
1078
        if (!$this->hasActiveSubClass()) {
1079
            return;
1080
        }
1081
1082
        $subClass = $this->getRequest()->query->get('subclass');
1083
1084
        if (!$this->hasSubClass($subClass)) {
1085
            return;
1086
        }
1087
1088
        return $subClass;
1089
    }
1090
1091
    /**
1092
     * {@inheritdoc}
1093
     */
1094
    public function getBatchActions()
1095
    {
1096
        $actions = array();
1097
1098
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1099
            $actions['delete'] = array(
1100
                'label' => 'action_delete',
1101
                'translation_domain' => 'SonataAdminBundle',
1102
                'ask_confirmation' => true, // by default always true
1103
            );
1104
        }
1105
1106
        $actions = $this->configureBatchActions($actions);
1107
1108
        foreach ($this->getExtensions() as $extension) {
1109
            // TODO: remove method check in next major release
1110
            if (method_exists($extension, 'configureBatchActions')) {
1111
                $actions = $extension->configureBatchActions($this, $actions);
1112
            }
1113
        }
1114
1115
        foreach ($actions  as $name => &$action) {
1116
            if (!array_key_exists('label', $action)) {
1117
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1118
            }
1119
1120
            if (!array_key_exists('translation_domain', $action)) {
1121
                $action['translation_domain'] = $this->getTranslationDomain();
1122
            }
1123
        }
1124
1125
        return $actions;
1126
    }
1127
1128
    /**
1129
     * {@inheritdoc}
1130
     */
1131
    public function getRoutes()
1132
    {
1133
        $this->buildRoutes();
1134
1135
        return $this->routes;
1136
    }
1137
1138
    /**
1139
     * {@inheritdoc}
1140
     */
1141
    public function getRouterIdParameter()
1142
    {
1143
        return $this->isChild() ? '{childId}' : '{id}';
1144
    }
1145
1146
    /**
1147
     * {@inheritdoc}
1148
     */
1149
    public function getIdParameter()
1150
    {
1151
        return $this->isChild() ? 'childId' : 'id';
1152
    }
1153
1154
    /**
1155
     * {@inheritdoc}
1156
     */
1157
    public function hasRoute($name)
1158
    {
1159
        if (!$this->routeGenerator) {
1160
            throw new \RuntimeException('RouteGenerator cannot be null');
1161
        }
1162
1163
        return $this->routeGenerator->hasAdminRoute($this, $name);
1164
    }
1165
1166
    /**
1167
     * {@inheritdoc}
1168
     */
1169
    public function isCurrentRoute($name, $adminCode = null)
1170
    {
1171
        if (!$this->hasRequest()) {
1172
            return false;
1173
        }
1174
1175
        $request = $this->getRequest();
1176
        $route = $request->get('_route');
1177
1178
        if ($adminCode) {
1179
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1180
        } else {
1181
            $admin = $this;
1182
        }
1183
1184
        if (!$admin) {
1185
            return false;
1186
        }
1187
1188
        return ($admin->getBaseRouteName().'_'.$name) == $route;
1189
    }
1190
1191
    /**
1192
     * {@inheritdoc}
1193
     */
1194
    public function generateObjectUrl($name, $object, array $parameters = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
1195
    {
1196
        $parameters['id'] = $this->getUrlsafeIdentifier($object);
1197
1198
        return $this->generateUrl($name, $parameters, $absolute);
1199
    }
1200
1201
    /**
1202
     * {@inheritdoc}
1203
     */
1204
    public function generateUrl($name, array $parameters = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
1205
    {
1206
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $absolute);
1207
    }
1208
1209
    /**
1210
     * {@inheritdoc}
1211
     */
1212
    public function generateMenuUrl($name, array $parameters = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
1213
    {
1214
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $absolute);
1215
    }
1216
1217
    /**
1218
     * @param array $templates
1219
     */
1220
    public function setTemplates(array $templates)
1221
    {
1222
        $this->templates = $templates;
1223
    }
1224
1225
    /**
1226
     * @param string $name
1227
     * @param string $template
1228
     */
1229
    public function setTemplate($name, $template)
1230
    {
1231
        $this->templates[$name] = $template;
1232
    }
1233
1234
    /**
1235
     * @return array
1236
     */
1237
    public function getTemplates()
1238
    {
1239
        return $this->templates;
1240
    }
1241
1242
    /**
1243
     * {@inheritdoc}
1244
     */
1245
    public function getTemplate($name)
1246
    {
1247
        if (isset($this->templates[$name])) {
1248
            return $this->templates[$name];
1249
        }
1250
    }
1251
1252
    /**
1253
     * {@inheritdoc}
1254
     */
1255
    public function getNewInstance()
1256
    {
1257
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1258
        foreach ($this->getExtensions() as $extension) {
1259
            $extension->alterNewInstance($this, $object);
1260
        }
1261
1262
        return $object;
1263
    }
1264
1265
    /**
1266
     * {@inheritdoc}
1267
     */
1268
    public function getFormBuilder()
1269
    {
1270
        $this->formOptions['data_class'] = $this->getClass();
1271
1272
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1273
            $this->getUniqid(),
1274
            $this->formOptions
1275
        );
1276
1277
        $this->defineFormBuilder($formBuilder);
1278
1279
        return $formBuilder;
1280
    }
1281
1282
    /**
1283
     * This method is being called by the main admin class and the child class,
1284
     * the getFormBuilder is only call by the main admin class.
1285
     *
1286
     * @param FormBuilderInterface $formBuilder
1287
     */
1288
    public function defineFormBuilder(FormBuilderInterface $formBuilder)
1289
    {
1290
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1291
1292
        $this->configureFormFields($mapper);
1293
1294
        foreach ($this->getExtensions() as $extension) {
1295
            $extension->configureFormFields($mapper);
1296
        }
1297
1298
        $this->attachInlineValidator();
1299
    }
1300
1301
    /**
1302
     * {@inheritdoc}
1303
     */
1304
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
1305
    {
1306
        $pool = $this->getConfigurationPool();
1307
1308
        $adminCode = $fieldDescription->getOption('admin_code');
1309
1310
        if ($adminCode !== null) {
1311
            $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...
1312
        } else {
1313
            $admin = $pool->getAdminByClass($fieldDescription->getTargetEntity());
1314
        }
1315
1316
        if (!$admin) {
1317
            return;
1318
        }
1319
1320
        if ($this->hasRequest()) {
1321
            $admin->setRequest($this->getRequest());
1322
        }
1323
1324
        $fieldDescription->setAssociationAdmin($admin);
1325
    }
1326
1327
    /**
1328
     * {@inheritdoc}
1329
     */
1330
    public function getObject($id)
1331
    {
1332
        $object = $this->getModelManager()->find($this->getClass(), $id);
1333
        foreach ($this->getExtensions() as $extension) {
1334
            $extension->alterObject($this, $object);
1335
        }
1336
1337
        return $object;
1338
    }
1339
1340
    /**
1341
     * {@inheritdoc}
1342
     */
1343
    public function getForm()
1344
    {
1345
        $this->buildForm();
1346
1347
        return $this->form;
1348
    }
1349
1350
    /**
1351
     * {@inheritdoc}
1352
     */
1353
    public function getList()
1354
    {
1355
        $this->buildList();
1356
1357
        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\AdminInterface::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...
1358
    }
1359
1360
    /**
1361
     * {@inheritdoc}
1362
     */
1363
    public function createQuery($context = 'list')
1364
    {
1365
        if (func_num_args() > 0) {
1366
            @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...
1367
                'The $context argument of '.__METHOD__.' is deprecated since 3.3, to be removed in 4.0.',
1368
                E_USER_DEPRECATED
1369
            );
1370
        }
1371
        $query = $this->getModelManager()->createQuery($this->class);
1372
1373
        foreach ($this->extensions as $extension) {
1374
            $extension->configureQuery($this, $query, $context);
1375
        }
1376
1377
        return $query;
1378
    }
1379
1380
    /**
1381
     * {@inheritdoc}
1382
     */
1383
    public function getDatagrid()
1384
    {
1385
        $this->buildDatagrid();
1386
1387
        return $this->datagrid;
1388
    }
1389
1390
    /**
1391
     * {@inheritdoc}
1392
     */
1393
    public function buildTabMenu($action, AdminInterface $childAdmin = null)
1394
    {
1395
        if ($this->loaded['tab_menu']) {
1396
            return;
1397
        }
1398
1399
        $this->loaded['tab_menu'] = true;
1400
1401
        $menu = $this->menuFactory->createItem('root');
1402
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1403
        $menu->setExtra('translation_domain', $this->translationDomain);
1404
1405
        // Prevents BC break with KnpMenuBundle v1.x
1406
        if (method_exists($menu, 'setCurrentUri')) {
1407
            $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...
1408
        }
1409
1410
        $this->configureTabMenu($menu, $action, $childAdmin);
1411
1412
        foreach ($this->getExtensions() as $extension) {
1413
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1414
        }
1415
1416
        $this->menu = $menu;
1417
    }
1418
1419
    /**
1420
     * {@inheritdoc}
1421
     */
1422
    public function buildSideMenu($action, AdminInterface $childAdmin = null)
1423
    {
1424
        return $this->buildTabMenu($action, $childAdmin);
1425
    }
1426
1427
    /**
1428
     * @param string         $action
1429
     * @param AdminInterface $childAdmin
1430
     *
1431
     * @return ItemInterface
1432
     */
1433
    public function getSideMenu($action, AdminInterface $childAdmin = null)
1434
    {
1435
        if ($this->isChild()) {
1436
            return $this->getParent()->getSideMenu($action, $this);
1437
        }
1438
1439
        $this->buildSideMenu($action, $childAdmin);
1440
1441
        return $this->menu;
1442
    }
1443
1444
    /**
1445
     * Returns the root code.
1446
     *
1447
     * @return string the root code
1448
     */
1449
    public function getRootCode()
1450
    {
1451
        return $this->getRoot()->getCode();
1452
    }
1453
1454
    /**
1455
     * Returns the master admin.
1456
     *
1457
     * @return AbstractAdmin the root admin class
1458
     */
1459
    public function getRoot()
1460
    {
1461
        $parentFieldDescription = $this->getParentFieldDescription();
1462
1463
        if (!$parentFieldDescription) {
1464
            return $this;
1465
        }
1466
1467
        return $parentFieldDescription->getAdmin()->getRoot();
1468
    }
1469
1470
    /**
1471
     * {@inheritdoc}
1472
     */
1473
    public function setBaseControllerName($baseControllerName)
1474
    {
1475
        $this->baseControllerName = $baseControllerName;
1476
    }
1477
1478
    /**
1479
     * {@inheritdoc}
1480
     */
1481
    public function getBaseControllerName()
1482
    {
1483
        return $this->baseControllerName;
1484
    }
1485
1486
    /**
1487
     * @param string $label
1488
     */
1489
    public function setLabel($label)
1490
    {
1491
        $this->label = $label;
1492
    }
1493
1494
    /**
1495
     * {@inheritdoc}
1496
     */
1497
    public function getLabel()
1498
    {
1499
        return $this->label;
1500
    }
1501
1502
    /**
1503
     * @param bool $persist
1504
     */
1505
    public function setPersistFilters($persist)
1506
    {
1507
        $this->persistFilters = $persist;
1508
    }
1509
1510
    /**
1511
     * @param int $maxPerPage
1512
     */
1513
    public function setMaxPerPage($maxPerPage)
1514
    {
1515
        $this->maxPerPage = $maxPerPage;
1516
    }
1517
1518
    /**
1519
     * @return int
1520
     */
1521
    public function getMaxPerPage()
1522
    {
1523
        return $this->maxPerPage;
1524
    }
1525
1526
    /**
1527
     * @param int $maxPageLinks
1528
     */
1529
    public function setMaxPageLinks($maxPageLinks)
1530
    {
1531
        $this->maxPageLinks = $maxPageLinks;
1532
    }
1533
1534
    /**
1535
     * @return int
1536
     */
1537
    public function getMaxPageLinks()
1538
    {
1539
        return $this->maxPageLinks;
1540
    }
1541
1542
    /**
1543
     * {@inheritdoc}
1544
     */
1545
    public function getFormGroups()
1546
    {
1547
        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 1547 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getFormGroups of type array.
Loading history...
1548
    }
1549
1550
    /**
1551
     * {@inheritdoc}
1552
     */
1553
    public function setFormGroups(array $formGroups)
1554
    {
1555
        $this->formGroups = $formGroups;
1556
    }
1557
1558
    /**
1559
     * {@inheritdoc}
1560
     */
1561
    public function removeFieldFromFormGroup($key)
1562
    {
1563
        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...
1564
            unset($this->formGroups[$name]['fields'][$key]);
1565
1566
            if (empty($this->formGroups[$name]['fields'])) {
1567
                unset($this->formGroups[$name]);
1568
            }
1569
        }
1570
    }
1571
1572
    /**
1573
     * @param array $group
1574
     * @param array $keys
1575
     */
1576
    public function reorderFormGroup($group, array $keys)
1577
    {
1578
        $formGroups = $this->getFormGroups();
1579
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1580
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups() on line 1578 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...
1581
    }
1582
1583
    /**
1584
     * {@inheritdoc}
1585
     */
1586
    public function getFormTabs()
1587
    {
1588
        return $this->formTabs;
1589
    }
1590
1591
    /**
1592
     * {@inheritdoc}
1593
     */
1594
    public function setFormTabs(array $formTabs)
1595
    {
1596
        $this->formTabs = $formTabs;
1597
    }
1598
1599
    /**
1600
     * {@inheritdoc}
1601
     */
1602
    public function getShowTabs()
1603
    {
1604
        return $this->showTabs;
1605
    }
1606
1607
    /**
1608
     * {@inheritdoc}
1609
     */
1610
    public function setShowTabs(array $showTabs)
1611
    {
1612
        $this->showTabs = $showTabs;
1613
    }
1614
1615
    /**
1616
     * {@inheritdoc}
1617
     */
1618
    public function getShowGroups()
1619
    {
1620
        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 1620 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getShowGroups of type array.
Loading history...
1621
    }
1622
1623
    /**
1624
     * {@inheritdoc}
1625
     */
1626
    public function setShowGroups(array $showGroups)
1627
    {
1628
        $this->showGroups = $showGroups;
1629
    }
1630
1631
    /**
1632
     * {@inheritdoc}
1633
     */
1634
    public function reorderShowGroup($group, array $keys)
1635
    {
1636
        $showGroups = $this->getShowGroups();
1637
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1638
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups() on line 1636 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...
1639
    }
1640
1641
    /**
1642
     * {@inheritdoc}
1643
     */
1644
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
1645
    {
1646
        $this->parentFieldDescription = $parentFieldDescription;
1647
    }
1648
1649
    /**
1650
     * {@inheritdoc}
1651
     */
1652
    public function getParentFieldDescription()
1653
    {
1654
        return $this->parentFieldDescription;
1655
    }
1656
1657
    /**
1658
     * {@inheritdoc}
1659
     */
1660
    public function hasParentFieldDescription()
1661
    {
1662
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1663
    }
1664
1665
    /**
1666
     * {@inheritdoc}
1667
     */
1668
    public function setSubject($subject)
1669
    {
1670
        if (is_object($subject) && !is_a($subject, $this->class, true)) {
1671
            $message = <<<'EOT'
1672
You are trying to set entity an instance of "%s",
1673
which is not the one registered with this admin class ("%s").
1674
This is deprecated since 3.5 and will no longer be supported in 4.0.
1675
EOT;
1676
1677
            @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...
1678
                sprintf($message, get_class($subject), $this->class),
1679
                E_USER_DEPRECATED
1680
            ); // NEXT_MAJOR : throw an exception instead
1681
        }
1682
1683
        $this->subject = $subject;
1684
    }
1685
1686
    /**
1687
     * {@inheritdoc}
1688
     */
1689
    public function getSubject()
1690
    {
1691
        if ($this->subject === null && $this->request && !$this->hasParentFieldDescription()) {
1692
            $id = $this->request->get($this->getIdParameter());
1693
            $this->subject = $this->getModelManager()->find($this->class, $id);
1694
        }
1695
1696
        return $this->subject;
1697
    }
1698
1699
    /**
1700
     * {@inheritdoc}
1701
     */
1702
    public function hasSubject()
1703
    {
1704
        return $this->subject != null;
1705
    }
1706
1707
    /**
1708
     * {@inheritdoc}
1709
     */
1710
    public function getFormFieldDescriptions()
1711
    {
1712
        $this->buildForm();
1713
1714
        return $this->formFieldDescriptions;
1715
    }
1716
1717
    /**
1718
     * {@inheritdoc}
1719
     */
1720
    public function getFormFieldDescription($name)
1721
    {
1722
        return $this->hasFormFieldDescription($name) ? $this->formFieldDescriptions[$name] : null;
1723
    }
1724
1725
    /**
1726
     * Returns true if the admin has a FieldDescription with the given $name.
1727
     *
1728
     * @param string $name
1729
     *
1730
     * @return bool
1731
     */
1732
    public function hasFormFieldDescription($name)
1733
    {
1734
        return array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1735
    }
1736
1737
    /**
1738
     * {@inheritdoc}
1739
     */
1740
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1741
    {
1742
        $this->formFieldDescriptions[$name] = $fieldDescription;
1743
    }
1744
1745
    /**
1746
     * remove a FieldDescription.
1747
     *
1748
     * @param string $name
1749
     */
1750
    public function removeFormFieldDescription($name)
1751
    {
1752
        unset($this->formFieldDescriptions[$name]);
1753
    }
1754
1755
    /**
1756
     * build and return the collection of form FieldDescription.
1757
     *
1758
     * @return array collection of form FieldDescription
1759
     */
1760
    public function getShowFieldDescriptions()
1761
    {
1762
        $this->buildShow();
1763
1764
        return $this->showFieldDescriptions;
1765
    }
1766
1767
    /**
1768
     * Returns the form FieldDescription with the given $name.
1769
     *
1770
     * @param string $name
1771
     *
1772
     * @return FieldDescriptionInterface
1773
     */
1774
    public function getShowFieldDescription($name)
1775
    {
1776
        $this->buildShow();
1777
1778
        return $this->hasShowFieldDescription($name) ? $this->showFieldDescriptions[$name] : null;
1779
    }
1780
1781
    /**
1782
     * {@inheritdoc}
1783
     */
1784
    public function hasShowFieldDescription($name)
1785
    {
1786
        return array_key_exists($name, $this->showFieldDescriptions);
1787
    }
1788
1789
    /**
1790
     * {@inheritdoc}
1791
     */
1792
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1793
    {
1794
        $this->showFieldDescriptions[$name] = $fieldDescription;
1795
    }
1796
1797
    /**
1798
     * {@inheritdoc}
1799
     */
1800
    public function removeShowFieldDescription($name)
1801
    {
1802
        unset($this->showFieldDescriptions[$name]);
1803
    }
1804
1805
    /**
1806
     * {@inheritdoc}
1807
     */
1808
    public function getListFieldDescriptions()
1809
    {
1810
        $this->buildList();
1811
1812
        return $this->listFieldDescriptions;
1813
    }
1814
1815
    /**
1816
     * {@inheritdoc}
1817
     */
1818
    public function getListFieldDescription($name)
1819
    {
1820
        return $this->hasListFieldDescription($name) ? $this->listFieldDescriptions[$name] : null;
1821
    }
1822
1823
    /**
1824
     * {@inheritdoc}
1825
     */
1826
    public function hasListFieldDescription($name)
1827
    {
1828
        $this->buildList();
1829
1830
        return array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1831
    }
1832
1833
    /**
1834
     * {@inheritdoc}
1835
     */
1836
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1837
    {
1838
        $this->listFieldDescriptions[$name] = $fieldDescription;
1839
    }
1840
1841
    /**
1842
     * {@inheritdoc}
1843
     */
1844
    public function removeListFieldDescription($name)
1845
    {
1846
        unset($this->listFieldDescriptions[$name]);
1847
    }
1848
1849
    /**
1850
     * {@inheritdoc}
1851
     */
1852
    public function getFilterFieldDescription($name)
1853
    {
1854
        return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null;
1855
    }
1856
1857
    /**
1858
     * {@inheritdoc}
1859
     */
1860
    public function hasFilterFieldDescription($name)
1861
    {
1862
        return array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1863
    }
1864
1865
    /**
1866
     * {@inheritdoc}
1867
     */
1868
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1869
    {
1870
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1871
    }
1872
1873
    /**
1874
     * {@inheritdoc}
1875
     */
1876
    public function removeFilterFieldDescription($name)
1877
    {
1878
        unset($this->filterFieldDescriptions[$name]);
1879
    }
1880
1881
    /**
1882
     * {@inheritdoc}
1883
     */
1884
    public function getFilterFieldDescriptions()
1885
    {
1886
        $this->buildDatagrid();
1887
1888
        return $this->filterFieldDescriptions;
1889
    }
1890
1891
    /**
1892
     * {@inheritdoc}
1893
     */
1894
    public function addChild(AdminInterface $child)
1895
    {
1896
        $this->children[$child->getCode()] = $child;
1897
1898
        $child->setBaseCodeRoute($this->getCode().'|'.$child->getCode());
1899
        $child->setParent($this);
1900
    }
1901
1902
    /**
1903
     * {@inheritdoc}
1904
     */
1905
    public function hasChild($code)
1906
    {
1907
        return isset($this->children[$code]);
1908
    }
1909
1910
    /**
1911
     * {@inheritdoc}
1912
     */
1913
    public function getChildren()
1914
    {
1915
        return $this->children;
1916
    }
1917
1918
    /**
1919
     * {@inheritdoc}
1920
     */
1921
    public function getChild($code)
1922
    {
1923
        return $this->hasChild($code) ? $this->children[$code] : null;
1924
    }
1925
1926
    /**
1927
     * {@inheritdoc}
1928
     */
1929
    public function setParent(AdminInterface $parent)
1930
    {
1931
        $this->parent = $parent;
1932
    }
1933
1934
    /**
1935
     * {@inheritdoc}
1936
     */
1937
    public function getParent()
1938
    {
1939
        return $this->parent;
1940
    }
1941
1942
    /**
1943
     * {@inheritdoc}
1944
     */
1945
    public function isChild()
1946
    {
1947
        return $this->parent instanceof AdminInterface;
1948
    }
1949
1950
    /**
1951
     * Returns true if the admin has children, false otherwise.
1952
     *
1953
     * @return bool if the admin has children
1954
     */
1955
    public function hasChildren()
1956
    {
1957
        return count($this->children) > 0;
1958
    }
1959
1960
    /**
1961
     * {@inheritdoc}
1962
     */
1963
    public function setUniqid($uniqid)
1964
    {
1965
        $this->uniqid = $uniqid;
1966
    }
1967
1968
    /**
1969
     * {@inheritdoc}
1970
     */
1971
    public function getUniqid()
1972
    {
1973
        if (!$this->uniqid) {
1974
            $this->uniqid = 's'.uniqid();
1975
        }
1976
1977
        return $this->uniqid;
1978
    }
1979
1980
    /**
1981
     * Returns the classname label.
1982
     *
1983
     * @return string the classname label
1984
     */
1985
    public function getClassnameLabel()
1986
    {
1987
        return $this->classnameLabel;
1988
    }
1989
1990
    /**
1991
     * {@inheritdoc}
1992
     */
1993
    public function getPersistentParameters()
1994
    {
1995
        $parameters = array();
1996
1997
        foreach ($this->getExtensions() as $extension) {
1998
            $params = $extension->getPersistentParameters($this);
1999
2000
            if (!is_array($params)) {
2001
                throw new \RuntimeException(sprintf('The %s::getPersistentParameters must return an array', get_class($extension)));
2002
            }
2003
2004
            $parameters = array_merge($parameters, $params);
2005
        }
2006
2007
        return $parameters;
2008
    }
2009
2010
    /**
2011
     * @param string $name
2012
     *
2013
     * @return null|mixed
2014
     */
2015
    public function getPersistentParameter($name)
2016
    {
2017
        $parameters = $this->getPersistentParameters();
2018
2019
        return isset($parameters[$name]) ? $parameters[$name] : null;
2020
    }
2021
2022
    /**
2023
     * {@inheritdoc}
2024
     */
2025
    public function getBreadcrumbs($action)
2026
    {
2027
        @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...
2028
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2029
            ' Use Sonata\AdminBundle\Admin\BreadcrumbsBuilder::getBreadcrumbs instead.',
2030
            E_USER_DEPRECATED
2031
        );
2032
2033
        return $this->getBreadcrumbsBuilder()->getBreadcrumbs($this, $action);
2034
    }
2035
2036
    /**
2037
     * Generates the breadcrumbs array.
2038
     *
2039
     * Note: the method will be called by the top admin instance (parent => child)
2040
     *
2041
     * @param string             $action
2042
     * @param ItemInterface|null $menu
2043
     *
2044
     * @return array
2045
     */
2046
    public function buildBreadcrumbs($action, MenuItemInterface $menu = null)
2047
    {
2048
        @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...
2049
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.',
2050
            E_USER_DEPRECATED
2051
        );
2052
2053
        if (isset($this->breadcrumbs[$action])) {
2054
            return $this->breadcrumbs[$action];
2055
        }
2056
2057
        return $this->breadcrumbs[$action] = $this->getBreadcrumbsBuilder()
2058
            ->buildBreadcrumbs($this, $action, $menu);
2059
    }
2060
2061
    /**
2062
     * NEXT_MAJOR : remove this method.
2063
     *
2064
     * @return BreadcrumbsBuilderInterface
2065
     */
2066
    final public function getBreadcrumbsBuilder()
2067
    {
2068
        @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...
2069
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2070
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
2071
            E_USER_DEPRECATED
2072
        );
2073
        if ($this->breadcrumbsBuilder === null) {
2074
            $this->breadcrumbsBuilder = new BreadcrumbsBuilder(
2075
                $this->getConfigurationPool()->getContainer()->getParameter('sonata.admin.configuration.breadcrumbs')
2076
            );
2077
        }
2078
2079
        return $this->breadcrumbsBuilder;
2080
    }
2081
2082
    /**
2083
     * NEXT_MAJOR : remove this method.
2084
     *
2085
     * @param BreadcrumbsBuilderInterface
2086
     *
2087
     * @return AbstractAdmin
2088
     */
2089
    final public function setBreadcrumbsBuilder(BreadcrumbsBuilderInterface $value)
2090
    {
2091
        @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...
2092
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2093
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
2094
            E_USER_DEPRECATED
2095
        );
2096
        $this->breadcrumbsBuilder = $value;
2097
2098
        return $this;
2099
    }
2100
2101
    /**
2102
     * {@inheritdoc}
2103
     */
2104
    public function setCurrentChild($currentChild)
2105
    {
2106
        $this->currentChild = $currentChild;
2107
    }
2108
2109
    /**
2110
     * {@inheritdoc}
2111
     */
2112
    public function getCurrentChild()
2113
    {
2114
        return $this->currentChild;
2115
    }
2116
2117
    /**
2118
     * Returns the current child admin instance.
2119
     *
2120
     * @return AdminInterface|null the current child admin instance
2121
     */
2122
    public function getCurrentChildAdmin()
2123
    {
2124
        foreach ($this->children as $children) {
2125
            if ($children->getCurrentChild()) {
2126
                return $children;
2127
            }
2128
        }
2129
2130
        return;
2131
    }
2132
2133
    /**
2134
     * {@inheritdoc}
2135
     */
2136
    public function trans($id, array $parameters = array(), $domain = null, $locale = null)
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.9 and will be removed in 4.0.',
2140
            E_USER_DEPRECATED
2141
        );
2142
2143
        $domain = $domain ?: $this->getTranslationDomain();
2144
2145
        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...
2146
    }
2147
2148
    /**
2149
     * Translate a message id.
2150
     *
2151
     * NEXT_MAJOR: remove this method
2152
     *
2153
     * @param string      $id
2154
     * @param int         $count
2155
     * @param array       $parameters
2156
     * @param string|null $domain
2157
     * @param string|null $locale
2158
     *
2159
     * @return string the translated string
2160
     *
2161
     * @deprecated since 3.9, to be removed with 4.0
2162
     */
2163
    public function transChoice($id, $count, array $parameters = array(), $domain = null, $locale = null)
2164
    {
2165
        @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...
2166
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2167
            E_USER_DEPRECATED
2168
        );
2169
2170
        $domain = $domain ?: $this->getTranslationDomain();
2171
2172
        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...
2173
    }
2174
2175
    /**
2176
     * {@inheritdoc}
2177
     */
2178
    public function setTranslationDomain($translationDomain)
2179
    {
2180
        $this->translationDomain = $translationDomain;
2181
    }
2182
2183
    /**
2184
     * {@inheritdoc}
2185
     */
2186
    public function getTranslationDomain()
2187
    {
2188
        return $this->translationDomain;
2189
    }
2190
2191
    /**
2192
     * {@inheritdoc}
2193
     *
2194
     * NEXT_MAJOR: remove this method
2195
     *
2196
     * @deprecated since 3.9, to be removed with 4.0
2197
     */
2198
    public function setTranslator(TranslatorInterface $translator)
2199
    {
2200
        $args = func_get_args();
2201
        if (isset($args[1]) && $args[1]) {
2202
            @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...
2203
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2204
                E_USER_DEPRECATED
2205
            );
2206
        }
2207
2208
        $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...
2209
    }
2210
2211
    /**
2212
     * {@inheritdoc}
2213
     *
2214
     * NEXT_MAJOR: remove this method
2215
     *
2216
     * @deprecated since 3.9, to be removed with 4.0
2217
     */
2218
    public function getTranslator()
2219
    {
2220
        @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...
2221
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2222
            E_USER_DEPRECATED
2223
        );
2224
2225
        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...
2226
    }
2227
2228
    /**
2229
     * {@inheritdoc}
2230
     */
2231
    public function getTranslationLabel($label, $context = '', $type = '')
2232
    {
2233
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2234
    }
2235
2236
    /**
2237
     * {@inheritdoc}
2238
     */
2239
    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...
2240
    {
2241
        $this->request = $request;
2242
2243
        foreach ($this->getChildren() as $children) {
2244
            $children->setRequest($request);
2245
        }
2246
    }
2247
2248
    /**
2249
     * {@inheritdoc}
2250
     */
2251
    public function getRequest()
2252
    {
2253
        if (!$this->request) {
2254
            throw new \RuntimeException('The Request object has not been set');
2255
        }
2256
2257
        return $this->request;
2258
    }
2259
2260
    /**
2261
     * {@inheritdoc}
2262
     */
2263
    public function hasRequest()
2264
    {
2265
        return $this->request !== null;
2266
    }
2267
2268
    /**
2269
     * {@inheritdoc}
2270
     */
2271
    public function setFormContractor(FormContractorInterface $formBuilder)
2272
    {
2273
        $this->formContractor = $formBuilder;
2274
    }
2275
2276
    /**
2277
     * @return FormContractorInterface
2278
     */
2279
    public function getFormContractor()
2280
    {
2281
        return $this->formContractor;
2282
    }
2283
2284
    /**
2285
     * {@inheritdoc}
2286
     */
2287
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder)
2288
    {
2289
        $this->datagridBuilder = $datagridBuilder;
2290
    }
2291
2292
    /**
2293
     * {@inheritdoc}
2294
     */
2295
    public function getDatagridBuilder()
2296
    {
2297
        return $this->datagridBuilder;
2298
    }
2299
2300
    /**
2301
     * {@inheritdoc}
2302
     */
2303
    public function setListBuilder(ListBuilderInterface $listBuilder)
2304
    {
2305
        $this->listBuilder = $listBuilder;
2306
    }
2307
2308
    /**
2309
     * {@inheritdoc}
2310
     */
2311
    public function getListBuilder()
2312
    {
2313
        return $this->listBuilder;
2314
    }
2315
2316
    /**
2317
     * @param ShowBuilderInterface $showBuilder
2318
     */
2319
    public function setShowBuilder(ShowBuilderInterface $showBuilder)
2320
    {
2321
        $this->showBuilder = $showBuilder;
2322
    }
2323
2324
    /**
2325
     * @return ShowBuilderInterface
2326
     */
2327
    public function getShowBuilder()
2328
    {
2329
        return $this->showBuilder;
2330
    }
2331
2332
    /**
2333
     * {@inheritdoc}
2334
     */
2335
    public function setConfigurationPool(Pool $configurationPool)
2336
    {
2337
        $this->configurationPool = $configurationPool;
2338
    }
2339
2340
    /**
2341
     * @return Pool
2342
     */
2343
    public function getConfigurationPool()
2344
    {
2345
        return $this->configurationPool;
2346
    }
2347
2348
    /**
2349
     * {@inheritdoc}
2350
     */
2351
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator)
2352
    {
2353
        $this->routeGenerator = $routeGenerator;
2354
    }
2355
2356
    /**
2357
     * @return RouteGeneratorInterface
2358
     */
2359
    public function getRouteGenerator()
2360
    {
2361
        return $this->routeGenerator;
2362
    }
2363
2364
    /**
2365
     * {@inheritdoc}
2366
     */
2367
    public function getCode()
2368
    {
2369
        return $this->code;
2370
    }
2371
2372
    /**
2373
     * @param string $baseCodeRoute
2374
     */
2375
    public function setBaseCodeRoute($baseCodeRoute)
2376
    {
2377
        $this->baseCodeRoute = $baseCodeRoute;
2378
    }
2379
2380
    /**
2381
     * {@inheritdoc}
2382
     */
2383
    public function getBaseCodeRoute()
2384
    {
2385
        return $this->baseCodeRoute;
2386
    }
2387
2388
    /**
2389
     * {@inheritdoc}
2390
     */
2391
    public function getModelManager()
2392
    {
2393
        return $this->modelManager;
2394
    }
2395
2396
    /**
2397
     * @param ModelManagerInterface $modelManager
2398
     */
2399
    public function setModelManager(ModelManagerInterface $modelManager)
2400
    {
2401
        $this->modelManager = $modelManager;
2402
    }
2403
2404
    /**
2405
     * {@inheritdoc}
2406
     */
2407
    public function getManagerType()
2408
    {
2409
        return $this->managerType;
2410
    }
2411
2412
    /**
2413
     * @param string $type
2414
     */
2415
    public function setManagerType($type)
2416
    {
2417
        $this->managerType = $type;
2418
    }
2419
2420
    /**
2421
     * {@inheritdoc}
2422
     */
2423
    public function getObjectIdentifier()
2424
    {
2425
        return $this->getCode();
2426
    }
2427
2428
    /**
2429
     * Set the roles and permissions per role.
2430
     *
2431
     * @param array $information
2432
     */
2433
    public function setSecurityInformation(array $information)
2434
    {
2435
        $this->securityInformation = $information;
2436
    }
2437
2438
    /**
2439
     * {@inheritdoc}
2440
     */
2441
    public function getSecurityInformation()
2442
    {
2443
        return $this->securityInformation;
2444
    }
2445
2446
    /**
2447
     * Return the list of permissions the user should have in order to display the admin.
2448
     *
2449
     * @param string $context
2450
     *
2451
     * @return array
2452
     */
2453
    public function getPermissionsShow($context)
2454
    {
2455
        switch ($context) {
2456
            case self::CONTEXT_DASHBOARD:
2457
            case self::CONTEXT_MENU:
2458
            default:
2459
                return array('LIST');
2460
        }
2461
    }
2462
2463
    /**
2464
     * {@inheritdoc}
2465
     */
2466
    public function showIn($context)
2467
    {
2468
        switch ($context) {
2469
            case self::CONTEXT_DASHBOARD:
2470
            case self::CONTEXT_MENU:
2471
            default:
2472
                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...
2473
        }
2474
    }
2475
2476
    /**
2477
     * {@inheritdoc}
2478
     */
2479
    public function createObjectSecurity($object)
2480
    {
2481
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2482
    }
2483
2484
    /**
2485
     * {@inheritdoc}
2486
     */
2487
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
2488
    {
2489
        $this->securityHandler = $securityHandler;
2490
    }
2491
2492
    /**
2493
     * {@inheritdoc}
2494
     */
2495
    public function getSecurityHandler()
2496
    {
2497
        return $this->securityHandler;
2498
    }
2499
2500
    /**
2501
     * {@inheritdoc}
2502
     */
2503
    public function isGranted($name, $object = null)
2504
    {
2505
        $key = md5(json_encode($name).($object ? '/'.spl_object_hash($object) : ''));
2506
2507
        if (!array_key_exists($key, $this->cacheIsGranted)) {
2508
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2509
        }
2510
2511
        return $this->cacheIsGranted[$key];
2512
    }
2513
2514
    /**
2515
     * {@inheritdoc}
2516
     */
2517
    public function getUrlsafeIdentifier($entity)
2518
    {
2519
        return $this->getModelManager()->getUrlsafeIdentifier($entity);
2520
    }
2521
2522
    /**
2523
     * {@inheritdoc}
2524
     */
2525
    public function getNormalizedIdentifier($entity)
2526
    {
2527
        return $this->getModelManager()->getNormalizedIdentifier($entity);
2528
    }
2529
2530
    /**
2531
     * {@inheritdoc}
2532
     */
2533
    public function id($entity)
2534
    {
2535
        return $this->getNormalizedIdentifier($entity);
2536
    }
2537
2538
    /**
2539
     * {@inheritdoc}
2540
     */
2541
    public function setValidator($validator)
2542
    {
2543
        // TODO: Remove it when bumping requirements to SF 2.5+
2544
        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...
2545
            throw new \InvalidArgumentException(
2546
                'Argument 1 must be an instance of Symfony\Component\Validator\Validator\ValidatorInterface'
2547
                .' or Symfony\Component\Validator\ValidatorInterface'
2548
            );
2549
        }
2550
2551
        $this->validator = $validator;
2552
    }
2553
2554
    /**
2555
     * {@inheritdoc}
2556
     */
2557
    public function getValidator()
2558
    {
2559
        return $this->validator;
2560
    }
2561
2562
    /**
2563
     * {@inheritdoc}
2564
     */
2565
    public function getShow()
2566
    {
2567
        $this->buildShow();
2568
2569
        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...
2570
    }
2571
2572
    /**
2573
     * {@inheritdoc}
2574
     */
2575
    public function setFormTheme(array $formTheme)
2576
    {
2577
        $this->formTheme = $formTheme;
2578
    }
2579
2580
    /**
2581
     * {@inheritdoc}
2582
     */
2583
    public function getFormTheme()
2584
    {
2585
        return $this->formTheme;
2586
    }
2587
2588
    /**
2589
     * {@inheritdoc}
2590
     */
2591
    public function setFilterTheme(array $filterTheme)
2592
    {
2593
        $this->filterTheme = $filterTheme;
2594
    }
2595
2596
    /**
2597
     * {@inheritdoc}
2598
     */
2599
    public function getFilterTheme()
2600
    {
2601
        return $this->filterTheme;
2602
    }
2603
2604
    /**
2605
     * {@inheritdoc}
2606
     */
2607
    public function addExtension(AdminExtensionInterface $extension)
2608
    {
2609
        $this->extensions[] = $extension;
2610
    }
2611
2612
    /**
2613
     * {@inheritdoc}
2614
     */
2615
    public function getExtensions()
2616
    {
2617
        return $this->extensions;
2618
    }
2619
2620
    /**
2621
     * {@inheritdoc}
2622
     */
2623
    public function setMenuFactory(MenuFactoryInterface $menuFactory)
2624
    {
2625
        $this->menuFactory = $menuFactory;
2626
    }
2627
2628
    /**
2629
     * {@inheritdoc}
2630
     */
2631
    public function getMenuFactory()
2632
    {
2633
        return $this->menuFactory;
2634
    }
2635
2636
    /**
2637
     * {@inheritdoc}
2638
     */
2639
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder)
2640
    {
2641
        $this->routeBuilder = $routeBuilder;
2642
    }
2643
2644
    /**
2645
     * {@inheritdoc}
2646
     */
2647
    public function getRouteBuilder()
2648
    {
2649
        return $this->routeBuilder;
2650
    }
2651
2652
    /**
2653
     * {@inheritdoc}
2654
     */
2655
    public function toString($object)
2656
    {
2657
        if (!is_object($object)) {
2658
            return '';
2659
        }
2660
2661
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2662
            return (string) $object;
2663
        }
2664
2665
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2666
    }
2667
2668
    /**
2669
     * {@inheritdoc}
2670
     */
2671
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy)
2672
    {
2673
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2674
    }
2675
2676
    /**
2677
     * {@inheritdoc}
2678
     */
2679
    public function getLabelTranslatorStrategy()
2680
    {
2681
        return $this->labelTranslatorStrategy;
2682
    }
2683
2684
    /**
2685
     * {@inheritdoc}
2686
     */
2687
    public function supportsPreviewMode()
2688
    {
2689
        return $this->supportsPreviewMode;
2690
    }
2691
2692
    /**
2693
     * Set custom per page options.
2694
     *
2695
     * @param array $options
2696
     */
2697
    public function setPerPageOptions(array $options)
2698
    {
2699
        $this->perPageOptions = $options;
2700
    }
2701
2702
    /**
2703
     * Returns predefined per page options.
2704
     *
2705
     * @return array
2706
     */
2707
    public function getPerPageOptions()
2708
    {
2709
        return $this->perPageOptions;
2710
    }
2711
2712
    /**
2713
     * Set pager type.
2714
     *
2715
     * @param string $pagerType
2716
     */
2717
    public function setPagerType($pagerType)
2718
    {
2719
        $this->pagerType = $pagerType;
2720
    }
2721
2722
    /**
2723
     * Get pager type.
2724
     *
2725
     * @return string
2726
     */
2727
    public function getPagerType()
2728
    {
2729
        return $this->pagerType;
2730
    }
2731
2732
    /**
2733
     * Returns true if the per page value is allowed, false otherwise.
2734
     *
2735
     * @param int $perPage
2736
     *
2737
     * @return bool
2738
     */
2739
    public function determinedPerPageValue($perPage)
2740
    {
2741
        return in_array($perPage, $this->perPageOptions);
2742
    }
2743
2744
    /**
2745
     * {@inheritdoc}
2746
     */
2747
    public function isAclEnabled()
2748
    {
2749
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2750
    }
2751
2752
    /**
2753
     * {@inheritdoc}
2754
     */
2755
    public function getObjectMetadata($object)
2756
    {
2757
        return new Metadata($this->toString($object));
2758
    }
2759
2760
    /**
2761
     * {@inheritdoc}
2762
     */
2763
    public function getListModes()
2764
    {
2765
        return $this->listModes;
2766
    }
2767
2768
    /**
2769
     * {@inheritdoc}
2770
     */
2771
    public function setListMode($mode)
2772
    {
2773
        if (!$this->hasRequest()) {
2774
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2775
        }
2776
2777
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2778
    }
2779
2780
    /**
2781
     * {@inheritdoc}
2782
     */
2783
    public function getListMode()
2784
    {
2785
        if (!$this->hasRequest()) {
2786
            return 'list';
2787
        }
2788
2789
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2790
    }
2791
2792
    /**
2793
     * {@inheritdoc}
2794
     */
2795
    public function getAccessMapping()
2796
    {
2797
        return $this->accessMapping;
2798
    }
2799
2800
    /**
2801
     * {@inheritdoc}
2802
     */
2803
    public function checkAccess($action, $object = null)
2804
    {
2805
        $access = $this->getAccess();
2806
2807
        if (!array_key_exists($action, $access)) {
2808
            throw new \InvalidArgumentException(sprintf(
2809
                'Action "%s" could not be found in access mapping.'
2810
                .' Please make sure your action is defined into your admin class accessMapping property.',
2811
                $action
2812
            ));
2813
        }
2814
2815
        if (!is_array($access[$action])) {
2816
            $access[$action] = array($access[$action]);
2817
        }
2818
2819
        foreach ($access[$action] as $role) {
2820
            if (false === $this->isGranted($role, $object)) {
2821
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2822
            }
2823
        }
2824
    }
2825
2826
    /**
2827
     * Hook to handle access authorization, without throw Exception.
2828
     *
2829
     * @param string $action
2830
     * @param object $object
2831
     *
2832
     * @return bool
2833
     */
2834
    public function hasAccess($action, $object = null)
2835
    {
2836
        $access = $this->getAccess();
2837
2838
        if (!array_key_exists($action, $access)) {
2839
            return false;
2840
        }
2841
2842
        if (!is_array($access[$action])) {
2843
            $access[$action] = array($access[$action]);
2844
        }
2845
2846
        foreach ($access[$action] as $role) {
2847
            if (false === $this->isGranted($role, $object)) {
2848
                return false;
2849
            }
2850
        }
2851
2852
        return true;
2853
    }
2854
2855
    /**
2856
     * {@inheritdoc}
2857
     */
2858
    public function configureActionButtons($action, $object = null)
2859
    {
2860
        $list = array();
2861
2862
        if (in_array($action, array('tree', 'show', 'edit', 'delete', 'list', 'batch'))
2863
            && $this->hasAccess('create')
2864
            && $this->hasRoute('create')
2865
        ) {
2866
            $list['create'] = array(
2867
                'template' => $this->getTemplate('button_create'),
2868
            );
2869
        }
2870
2871
        if (in_array($action, array('show', 'delete', 'acl', 'history'))
2872
            && $this->canAccessObject('edit', $object)
2873
            && $this->hasRoute('edit')
2874
        ) {
2875
            $list['edit'] = array(
2876
                'template' => $this->getTemplate('button_edit'),
2877
            );
2878
        }
2879
2880
        if (in_array($action, array('show', 'edit', 'acl'))
2881
            && $this->canAccessObject('history', $object)
2882
            && $this->hasRoute('history')
2883
        ) {
2884
            $list['history'] = array(
2885
                'template' => $this->getTemplate('button_history'),
2886
            );
2887
        }
2888
2889
        if (in_array($action, array('edit', 'history'))
2890
            && $this->isAclEnabled()
2891
            && $this->canAccessObject('acl', $object)
2892
            && $this->hasRoute('acl')
2893
        ) {
2894
            $list['acl'] = array(
2895
                'template' => $this->getTemplate('button_acl'),
2896
            );
2897
        }
2898
2899
        if (in_array($action, array('edit', 'history', 'acl'))
2900
            && $this->canAccessObject('show', $object)
2901
            && count($this->getShow()) > 0
2902
            && $this->hasRoute('show')
2903
        ) {
2904
            $list['show'] = array(
2905
                'template' => $this->getTemplate('button_show'),
2906
            );
2907
        }
2908
2909
        if (in_array($action, array('show', 'edit', 'delete', 'acl', 'batch'))
2910
            && $this->hasAccess('list')
2911
            && $this->hasRoute('list')
2912
        ) {
2913
            $list['list'] = array(
2914
                'template' => $this->getTemplate('button_list'),
2915
            );
2916
        }
2917
2918
        return $list;
2919
    }
2920
2921
    /**
2922
     * @param string $action
2923
     * @param mixed  $object
2924
     *
2925
     * @return array
2926
     */
2927
    public function getActionButtons($action, $object = null)
2928
    {
2929
        $list = $this->configureActionButtons($action, $object);
2930
2931
        foreach ($this->getExtensions() as $extension) {
2932
            // TODO: remove method check in next major release
2933
            if (method_exists($extension, 'configureActionButtons')) {
2934
                $list = $extension->configureActionButtons($this, $list, $action, $object);
2935
            }
2936
        }
2937
2938
        return $list;
2939
    }
2940
2941
    /**
2942
     * Get the list of actions that can be accessed directly from the dashboard.
2943
     *
2944
     * @return array
2945
     */
2946
    public function getDashboardActions()
2947
    {
2948
        $actions = array();
2949
2950
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2951
            $actions['create'] = array(
2952
                'label' => 'link_add',
2953
                'translation_domain' => 'SonataAdminBundle',
2954
                'template' => $this->getTemplate('action_create'),
2955
                'url' => $this->generateUrl('create'),
2956
                'icon' => 'plus-circle',
2957
            );
2958
        }
2959
2960
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2961
            $actions['list'] = array(
2962
                'label' => 'link_list',
2963
                'translation_domain' => 'SonataAdminBundle',
2964
                'url' => $this->generateUrl('list'),
2965
                'icon' => 'list',
2966
            );
2967
        }
2968
2969
        return $actions;
2970
    }
2971
2972
    /**
2973
     * Setting to true will enable mosaic button for the admin screen.
2974
     * Setting to false will hide mosaic button for the admin screen.
2975
     *
2976
     * @param bool $isShown
2977
     */
2978
    final public function showMosaicButton($isShown)
2979
    {
2980
        if ($isShown) {
2981
            $this->listModes['mosaic'] = array('class' => self::MOSAIC_ICON_CLASS);
2982
        } else {
2983
            unset($this->listModes['mosaic']);
2984
        }
2985
    }
2986
2987
    /**
2988
     * @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...
2989
     */
2990
    final public function getSearchResultLink($object)
2991
    {
2992
        foreach ($this->searchResultActions as $action) {
2993
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2994
                return $this->generateObjectUrl($action, $object);
2995
            }
2996
        }
2997
2998
        return;
2999
    }
3000
3001
    /**
3002
     * Checks if a filter type is set to a default value.
3003
     *
3004
     * @param string $name
3005
     *
3006
     * @return bool
3007
     */
3008
    final public function isDefaultFilter($name)
3009
    {
3010
        $filter = $this->getFilterParameters();
3011
        $default = $this->getDefaultFilterValues();
3012
3013
        if (!array_key_exists($name, $filter) || !array_key_exists($name, $default)) {
3014
            return false;
3015
        }
3016
3017
        return $filter[$name] == $default[$name];
3018
    }
3019
3020
    /**
3021
     * Check object existence and access, without throw Exception.
3022
     *
3023
     * @param string $action
3024
     * @param object $object
3025
     *
3026
     * @return bool
3027
     */
3028
    public function canAccessObject($action, $object)
3029
    {
3030
        return $object && $this->id($object) && $this->hasAccess($action, $object);
3031
    }
3032
    
3033
    public function iDoNothing()
3034
    {
3035
        return null;
3036
    }
3037
3038
    /**
3039
     * Returns a list of default filters.
3040
     *
3041
     * @return array
3042
     */
3043
    final protected function getDefaultFilterValues()
3044
    {
3045
        $defaultFilterValues = array();
3046
3047
        $this->configureDefaultFilterValues($defaultFilterValues);
3048
3049
        foreach ($this->getExtensions() as $extension) {
3050
            // NEXT_MAJOR: remove method check in next major release
3051
            if (method_exists($extension, 'configureDefaultFilterValues')) {
3052
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
3053
            }
3054
        }
3055
3056
        return $defaultFilterValues;
3057
    }
3058
3059
    /**
3060
     * {@inheritdoc}
3061
     */
3062
    protected function configureFormFields(FormMapper $form)
3063
    {
3064
    }
3065
3066
    /**
3067
     * @param ListMapper $list
3068
     */
3069
    protected function configureListFields(ListMapper $list)
3070
    {
3071
    }
3072
3073
    /**
3074
     * @param DatagridMapper $filter
3075
     */
3076
    protected function configureDatagridFilters(DatagridMapper $filter)
3077
    {
3078
    }
3079
3080
    /**
3081
     * @param ShowMapper $show
3082
     */
3083
    protected function configureShowFields(ShowMapper $show)
3084
    {
3085
    }
3086
3087
    /**
3088
     * @param RouteCollection $collection
3089
     */
3090
    protected function configureRoutes(RouteCollection $collection)
3091
    {
3092
    }
3093
3094
    /**
3095
     * Allows you to customize batch actions.
3096
     *
3097
     * @param array $actions List of actions
3098
     *
3099
     * @return array
3100
     */
3101
    protected function configureBatchActions($actions)
3102
    {
3103
        return $actions;
3104
    }
3105
3106
    /**
3107
     * NEXT_MAJOR: remove this method.
3108
     *
3109
     * @param MenuItemInterface $menu
3110
     * @param                   $action
3111
     * @param AdminInterface    $childAdmin
3112
     *
3113
     * @return mixed
3114
     *
3115
     * @deprecated Use configureTabMenu instead
3116
     */
3117
    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...
3118
    {
3119
    }
3120
3121
    /**
3122
     * Configures the tab menu in your admin.
3123
     *
3124
     * @param MenuItemInterface $menu
3125
     * @param string            $action
3126
     * @param AdminInterface    $childAdmin
3127
     *
3128
     * @return mixed
3129
     */
3130
    protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
3131
    {
3132
        // Use configureSideMenu not to mess with previous overrides
3133
        // TODO remove once deprecation period is over
3134
        $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...
3135
    }
3136
3137
    /**
3138
     * build the view FieldDescription array.
3139
     */
3140
    protected function buildShow()
3141
    {
3142
        if ($this->show) {
3143
            return;
3144
        }
3145
3146
        $this->show = new FieldDescriptionCollection();
3147
        $mapper = new ShowMapper($this->showBuilder, $this->show, $this);
3148
3149
        $this->configureShowFields($mapper);
3150
3151
        foreach ($this->getExtensions() as $extension) {
3152
            $extension->configureShowFields($mapper);
3153
        }
3154
    }
3155
3156
    /**
3157
     * build the list FieldDescription array.
3158
     */
3159
    protected function buildList()
3160
    {
3161
        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...
3162
            return;
3163
        }
3164
3165
        $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...
3166
3167
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
3168
3169
        if (count($this->getBatchActions()) > 0) {
3170
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3171
                $this->getClass(),
3172
                'batch',
3173
                array(
3174
                    'label' => 'batch',
3175
                    'code' => '_batch',
3176
                    'sortable' => false,
3177
                    'virtual_field' => true,
3178
                )
3179
            );
3180
3181
            $fieldDescription->setAdmin($this);
3182
            $fieldDescription->setTemplate($this->getTemplate('batch'));
3183
3184
            $mapper->add($fieldDescription, 'batch');
3185
        }
3186
3187
        $this->configureListFields($mapper);
3188
3189
        foreach ($this->getExtensions() as $extension) {
3190
            $extension->configureListFields($mapper);
3191
        }
3192
3193
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
3194
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3195
                $this->getClass(),
3196
                'select',
3197
                array(
3198
                    'label' => false,
3199
                    'code' => '_select',
3200
                    'sortable' => false,
3201
                    'virtual_field' => false,
3202
                )
3203
            );
3204
3205
            $fieldDescription->setAdmin($this);
3206
            $fieldDescription->setTemplate($this->getTemplate('select'));
3207
3208
            $mapper->add($fieldDescription, 'select');
3209
        }
3210
    }
3211
3212
    /**
3213
     * Build the form FieldDescription collection.
3214
     */
3215
    protected function buildForm()
3216
    {
3217
        if ($this->form) {
3218
            return;
3219
        }
3220
3221
        // append parent object if any
3222
        // todo : clean the way the Admin class can retrieve set the object
3223
        if ($this->isChild() && $this->getParentAssociationMapping()) {
3224
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
3225
3226
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3227
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3228
3229
            $object = $this->getSubject();
3230
3231
            $value = $propertyAccessor->getValue($object, $propertyPath);
3232
3233
            if (is_array($value) || ($value instanceof \Traversable && $value instanceof \ArrayAccess)) {
3234
                $value[] = $parent;
3235
                $propertyAccessor->setValue($object, $propertyPath, $value);
3236
            } else {
3237
                $propertyAccessor->setValue($object, $propertyPath, $parent);
3238
            }
3239
        }
3240
3241
        $this->form = $this->getFormBuilder()->getForm();
3242
    }
3243
3244
    /**
3245
     * Gets the subclass corresponding to the given name.
3246
     *
3247
     * @param string $name The name of the sub class
3248
     *
3249
     * @return string the subclass
3250
     */
3251
    protected function getSubClass($name)
3252
    {
3253
        if ($this->hasSubClass($name)) {
3254
            return $this->subClasses[$name];
3255
        }
3256
3257
        throw new \RuntimeException(sprintf(
3258
            'Unable to find the subclass `%s` for admin `%s`',
3259
            $name,
3260
            get_class($this)
3261
        ));
3262
    }
3263
3264
    /**
3265
     * Attach the inline validator to the model metadata, this must be done once per admin.
3266
     */
3267
    protected function attachInlineValidator()
3268
    {
3269
        $admin = $this;
3270
3271
        // add the custom inline validation option
3272
        // TODO: Remove conditional method when bumping requirements to SF 2.5+
3273
        if (method_exists($this->validator, 'getMetadataFor')) {
3274
            $metadata = $this->validator->getMetadataFor($this->getClass());
3275
        } else {
3276
            $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...
3277
        }
3278
3279
        $metadata->addConstraint(new InlineConstraint(array(
3280
            'service' => $this,
3281
            'method' => function (ErrorElement $errorElement, $object) use ($admin) {
3282
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
3283
3284
                // This avoid the main validation to be cascaded to children
3285
                // The problem occurs when a model Page has a collection of Page as property
3286
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
3287
                    return;
3288
                }
3289
3290
                $admin->validate($errorElement, $object);
3291
3292
                foreach ($admin->getExtensions() as $extension) {
3293
                    $extension->validate($admin, $errorElement, $object);
3294
                }
3295
            },
3296
            'serializingWarning' => true,
3297
        )));
3298
    }
3299
3300
    /**
3301
     * Predefine per page options.
3302
     */
3303
    protected function predefinePerPageOptions()
3304
    {
3305
        array_unshift($this->perPageOptions, $this->maxPerPage);
3306
        $this->perPageOptions = array_unique($this->perPageOptions);
3307
        sort($this->perPageOptions);
3308
    }
3309
3310
    /**
3311
     * Return list routes with permissions name.
3312
     *
3313
     * @return array
3314
     */
3315
    protected function getAccess()
3316
    {
3317
        $access = array_merge(array(
3318
            'acl' => 'MASTER',
3319
            'export' => 'EXPORT',
3320
            'historyCompareRevisions' => 'EDIT',
3321
            'historyViewRevision' => 'EDIT',
3322
            'history' => 'EDIT',
3323
            'edit' => 'EDIT',
3324
            'show' => 'VIEW',
3325
            'create' => 'CREATE',
3326
            'delete' => 'DELETE',
3327
            'batchDelete' => 'DELETE',
3328
            'list' => 'LIST',
3329
        ), $this->getAccessMapping());
3330
3331
        foreach ($this->extensions as $extension) {
3332
            // TODO: remove method check in next major release
3333
            if (method_exists($extension, 'getAccessMapping')) {
3334
                $access = array_merge($access, $extension->getAccessMapping($this));
3335
            }
3336
        }
3337
3338
        return $access;
3339
    }
3340
3341
    /**
3342
     * Returns a list of default filters.
3343
     *
3344
     * @param array $filterValues
3345
     */
3346
    protected function configureDefaultFilterValues(array &$filterValues)
3347
    {
3348
    }
3349
3350
    /**
3351
     * Build all the related urls to the current admin.
3352
     */
3353
    private function buildRoutes()
3354
    {
3355
        if ($this->loaded['routes']) {
3356
            return;
3357
        }
3358
3359
        $this->loaded['routes'] = true;
3360
3361
        $this->routes = new RouteCollection(
3362
            $this->getBaseCodeRoute(),
3363
            $this->getBaseRouteName(),
3364
            $this->getBaseRoutePattern(),
3365
            $this->getBaseControllerName()
3366
        );
3367
3368
        $this->routeBuilder->build($this, $this->routes);
3369
3370
        $this->configureRoutes($this->routes);
3371
3372
        foreach ($this->getExtensions() as $extension) {
3373
            $extension->configureRoutes($this, $this->routes);
3374
        }
3375
    }
3376
}
3377