Completed
Push — wip-platform ( 03cba6...2999ab )
by
unknown
05:53 queued 02:49
created

CoreAdmin::renameShowTab()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 26
Code Lines 16

Duplication

Lines 26
Ratio 100 %

Importance

Changes 0
Metric Value
dl 26
loc 26
rs 8.439
c 0
b 0
f 0
cc 5
eloc 16
nc 5
nop 3
1
<?php
2
3
/*
4
 *
5
 * Copyright (C) 2015-2017 Libre Informatique
6
 *
7
 * This file is licenced under the GNU LGPL v3.
8
 * For the full copyright and license information, please view the LICENSE.md
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Blast\Bundle\CoreBundle\Admin;
13
14
use Sonata\AdminBundle\Datagrid\DatagridMapper;
15
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
16
use Sonata\AdminBundle\Datagrid\ListMapper;
17
use Sonata\AdminBundle\Form\FormMapper;
18
use Sonata\AdminBundle\Mapper\BaseMapper;
19
use Sonata\AdminBundle\Show\ShowMapper;
20
use Sonata\AdminBundle\Route\RouteCollection;
21
use Sonata\AdminBundle\Admin\AbstractAdmin as SonataAdmin;
22
use Sonata\DoctrineORMAdminBundle\Admin\FieldDescription;
23
use Blast\Bundle\CoreBundle\Tools\Reflection\ClassAnalyzer;
24
use Blast\Bundle\CoreBundle\Admin\Traits\CollectionsManager;
25
use Blast\Bundle\CoreBundle\Admin\Traits\Mapper;
26
use Blast\Bundle\CoreBundle\Admin\Traits\Templates;
27
use Blast\Bundle\CoreBundle\Admin\Traits\PreEvents;
28
use Blast\Bundle\CoreBundle\Admin\Traits\ManyToManyManager;
29
use Blast\Bundle\CoreBundle\Admin\Traits\Actions;
30
use Blast\Bundle\CoreBundle\Admin\Traits\ListActions;
31
use Blast\Bundle\CoreBundle\CodeGenerator\CodeGeneratorRegistry;
32
use Blast\Bundle\CoreBundle\Translator\LibrinfoLabelTranslatorStrategy;
33
use Symfony\Component\PropertyAccess\PropertyAccess;
34
35
abstract class CoreAdmin extends SonataAdmin implements \JsonSerializable
36
{
37
    use CollectionsManager,
38
        ManyToManyManager,
39
        Mapper,
40
        Templates,
41
        PreEvents,
42
        Actions,
43
        ListActions
44
    ;
45
46
    protected $extraTemplates = [];
47
48
    public function configure()
49
    {
50
        parent::configure();
51
52
        /* Default Translation Strategy if not set as admin service tags */
53
        /* @todo : find if it is a good idea or not */
54
        if (!($this->getLabelTranslatorStrategy() instanceof LibrinfoLabelTranslatorStrategy)) {
55
            $this->setLabelTranslatorStrategy(new LibrinfoLabelTranslatorStrategy());
56
        }
57
        /* Should always be */
58
        if ($this->getLabelTranslatorStrategy() instanceof LibrinfoLabelTranslatorStrategy) {
59
            $this->getLabelTranslatorStrategy()->setFix($this->getClassnameLabel());
60
        }
61
62
        /* @todo: apply TranslatorStrategy to form_tab and form_group and show_tab and
63
           ... warning it may impact code as it used in some postConfigureFormFields */
64
    }
65
66
    /**
67
     * Configure routes for list actions.
68
     *
69
     * @param RouteCollection $collection
70
     */
71
    protected function configureRoutes(RouteCollection $collection)
72
    {
73
        parent::configureRoutes($collection);
74
        $collection->add('duplicate', $this->getRouterIdParameter() . '/duplicate');
75
        $collection->add('generateEntityCode');
76
77
        /* Needed or not needed ...
78
         * in sonata-project/admin-bundle/Controller/CRUDController.php
79
         * the batchAction method
80
         * throw exception if the http method is not POST
81
        */
82
        if ($collection->get('batch')) {
83
            $collection->get('batch')->setMethods(['POST']);
84
        }
85
    }
86
87 View Code Duplication
    public function getBaseRouteName()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
88
    {
89
        $configuredBaseRoute = $this->getBaseRouteMapping();
90
91
        if (count($configuredBaseRoute) > 0) {
92
            $this->cachedBaseRouteName = null;
0 ignored issues
show
Bug introduced by
The property cachedBaseRouteName cannot be accessed from this context as it is declared private in class Sonata\AdminBundle\Admin\AbstractAdmin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
93
            if (isset($configuredBaseRoute['name']) && $this->baseRouteName === null) {
94
                $this->baseRouteName = $configuredBaseRoute['name'];
95
            }
96
        }
97
98
        return parent::getBaseRouteName();
99
    }
100
101 View Code Duplication
    public function getBaseRoutePattern()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
102
    {
103
        $configuredBaseRoute = $this->getBaseRouteMapping();
104
105
        if (count($configuredBaseRoute) > 0) {
106
            $this->cachedBaseRoutePattern = null;
0 ignored issues
show
Bug introduced by
The property cachedBaseRoutePattern cannot be accessed from this context as it is declared private in class Sonata\AdminBundle\Admin\AbstractAdmin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
107
            if (isset($configuredBaseRoute['pattern']) && $this->baseRoutePattern === null) {
108
                $this->baseRoutePattern = $configuredBaseRoute['pattern'];
109
            }
110
        }
111
112
        return parent::getBaseRoutePattern();
113
    }
114
115
    public function getFormTheme()
116
    {
117
        return array_merge($this->formTheme, $this->getFormThemeMapping());
118
    }
119
120
    /**
121
     * @param DatagridMapper $mapper
122
     */
123
    protected function configureDatagridFilters(DatagridMapper $mapper)
124
    {
125
        if (!$this->configureMapper($mapper)) {
126
            $this->fallbackConfiguration($mapper, __FUNCTION__);
127
        }
128
    }
129
130
    /**
131
     * @param ListMapper $mapper
132
     */
133
    protected function configureListFields(ListMapper $mapper)
134
    {
135
        if (!$this->configureMapper($mapper)) {
136
            $this->fallbackConfiguration($mapper, __FUNCTION__);
137
        }
138
    }
139
140
    /**
141
     * @param FormMapper $mapper
142
     */
143
    protected function configureFormFields(FormMapper $mapper)
144
    {
145
        if (!$this->configureMapper($mapper)) {
146
            $this->fallbackConfiguration($mapper, __FUNCTION__);
147
        }
148
    }
149
150
    /**
151
     * @param ShowMapper $mapper
152
     */
153
    protected function configureShowFields(ShowMapper $mapper)
154
    {
155
        if (!$this->configureMapper($mapper)) {
156
            $this->fallbackConfiguration($mapper, __FUNCTION__);
157
        }
158
    }
159
160
    /**
161
     * @param BaseMapper $mapper
162
     */
163
    protected function fixShowRoutes(BaseMapper $mapper)
164
    {
165
        foreach (['getShow', 'getList'] as $fct) {
166
            foreach ($this->$fct()->getElements() as $field) {
167
                if ($field instanceof FieldDescription) {
168
                    $options = $field->getOptions();
169
                    if ($options['route']['name'] != 'edit') {
170
                        continue;
171
                    }
172
173
                    $options['route']['name'] = 'show';
174
                    $field->setOptions($options);
175
                }
176
            }
177
        }
178
179
        return $this;
180
    }
181
182
    protected function getCurrentComposition()
183
    {
184
        // traits of the current Entity
185
        $classes = ClassAnalyzer::getTraits($this->getClass());
186
        // inheritance of the current Entity
187
        foreach (array_reverse([$this->getClass()] + class_parents($this->getClass())) as $class) {
188
            $classes[] = $class;
189
        }
190
        // inheritance of the current Admin
191
        foreach (array_reverse([$this->getOriginalClass()] + $this->getParentClasses()) as $admin) {
192
            $classes[] = $admin;
193
        }
194
195
        return $classes;
196
    }
197
198
    private function fallbackConfiguration(BaseMapper $mapper, $function)
199
    {
200
        // fallback
201
        $rm = new \ReflectionMethod($this->getParentClass(), $function);
202
        if ($rm->class == $this->getParentClass()) {
203
            $this->configureFields($function, $mapper, $this->getParentClass());
204
        }
205
    }
206
207
    /**
208
     * Returns the level of depth of an array.
209
     *
210
     * @param array $array
211
     * @param int   $level : do not use, just used for recursivity
212
     *
213
     * @return int : depth
214
     */
215
    private static function arrayDepth($array, $level = 0)
216
    {
217
        if (!$array) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $array 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...
218
            return $level;
219
        }
220
221
        if (!is_array($array)) {
222
            return $level;
223
        }
224
225
        ++$level;
226
        foreach ($array as $key => $value) {
227
            if (is_array($value)) {
228
                $level = $level < self::arrayDepth($value, $level) ? self::arrayDepth($value, $level) : $level;
229
            }
230
        }
231
232
        return $level;
233
    }
234
235
    protected function getOriginalClass()
236
    {
237
        return get_called_class();
238
    }
239
240
    protected function getParentClasses()
241
    {
242
        return class_parents($this->getOriginalClass());
243
    }
244
245
    protected function getParentClass()
246
    {
247
        return get_parent_class($this->getOriginalClass());
248
    }
249
250
    protected function getGrandParentClass()
251
    {
252
        return get_parent_class(get_parent_class($this->getOriginalClass()));
253
    }
254
255
    /**
256
     * @param string $view     'list', 'show', 'form', etc
257
     * @param string $template template name
258
     */
259
    public function addExtraTemplate($view, $template)
260
    {
261
        if (empty($this->extraTemplates[$view])) {
262
            $this->extraTemplates[$view] = [];
263
        }
264
        if (!in_array($template, $this->extraTemplates[$view])) {
265
            $this->extraTemplates[$view][] = $template;
266
        }
267
    }
268
269
    /**
270
     * @param string $view 'list', 'show', 'form', etc
271
     *
272
     * @return array array of template names
273
     */
274
    public function getExtraTemplates($view)
275
    {
276
        if (empty($this->extraTemplates[$view])) {
277
            $this->extraTemplates[$view] = [];
278
        }
279
280
        return $this->extraTemplates[$view];
281
    }
282
283
    /**
284
     * @param string $view 'list', 'show', 'form', etc
285
     * @param array  $link link (array keys should be: 'label', 'url', 'class', 'title')
286
     */
287
    public function addHelperLink($view, $link)
288
    {
289
        if (empty($this->helperLinks[$view])) {
290
            $this->helperLinks[$view] = [];
291
        }
292
293
        // Do not add links without URL
294
        if (empty($link['url'])) {
295
            return;
296
        }
297
298
        // Do not add two links with the same URL
299
        foreach ($this->helperLinks[$view] as $l) {
300
            if ($l['url'] == $link['url']) {
301
                return;
302
            }
303
        }
304
305
        $this->helperLinks[$view][] = $link;
306
    }
307
308
    /**
309
     * @param string $view 'list', 'show', 'form', etc
310
     *
311
     * @return array array of links (each link is an array with keys 'label', 'url', 'class' and 'title')
312
     */
313
    public function getHelperLinks($view)
314
    {
315
        if (empty($this->helperLinks[$view])) {
316
            $this->helperLinks[$view] = [];
317
        }
318
319
        return $this->helperLinks[$view];
320
    }
321
322
    /**
323
     * Checks if a Bundle is installed.
324
     *
325
     * @param string $bundle Bundle name or class FQN
326
     */
327
    public function bundleExists($bundle)
328
    {
329
        $kernelBundles = $this->getConfigurationPool()->getContainer()->getParameter('kernel.bundles');
330
        if (array_key_exists($bundle, $kernelBundles)) {
331
            return true;
332
        }
333
        if (in_array($bundle, $kernelBundles)) {
334
            return true;
335
        }
336
337
        return false;
338
    }
339
340
    /**
341
     * Rename a form tab after form fields have been configured.
342
     *
343
     * TODO: groups of the renamed tab are still prefixed with the old tab name
344
     *
345
     * @param type $tabName    the name of the tab to be renamed
346
     * @param type $newTabName the new name for the tab
347
     */
348 View Code Duplication
    public function renameFormTab($tabName, $newTabName, $keepOrder = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
349
    {
350
        $tabs = $this->getFormTabs();
351
352
        if (!$tabs) {
353
            return;
354
        }
355
356
        if (!isset($tabs[$tabName])) {
357
            throw new \Exception(sprintf('Tab %s does not exist.', $tabName));
358
        }
359
        if (isset($tabs[$newTabName])) {
360
            return;
361
        }
362
363
        if ($keepOrder) {
364
            $keys = array_keys($tabs);
365
            $keys[array_search($tabName, $keys)] = $newTabName;
366
            $tabs = array_combine($keys, $tabs);
367
        } else {
368
            $tabs[$newTabName] = $tabs[$tabName];
369
            unset($tabs[$tabName]);
370
        }
371
372
        $this->setFormTabs($tabs);
373
    }
374
375
    /**
376
     * Rename a show tab after show fields have been configured.
377
     *
378
     * TODO: groups of the renamed tab are still prefixed with the old tab name
379
     *
380
     * @param type $tabName    the name of the tab to be renamed
381
     * @param type $newTabName the new name for the tab
382
     */
383 View Code Duplication
    public function renameShowTab($tabName, $newTabName, $keepOrder = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
384
    {
385
        $tabs = $this->getShowTabs();
386
387
        if (!$tabs) {
388
            return;
389
        }
390
391
        if (!isset($tabs[$tabName])) {
392
            throw new \Exception(sprintf('Tab %s does not exist.', $tabName));
393
        }
394
        if (isset($tabs[$newTabName])) {
395
            return;
396
        }
397
398
        if ($keepOrder) {
399
            $keys = array_keys($tabs);
400
            $keys[array_search($tabName, $keys)] = $newTabName;
401
            $tabs = array_combine($keys, $tabs);
402
        } else {
403
            $tabs[$newTabName] = $tabs[$tabName];
404
            unset($tabs[$tabName]);
405
        }
406
407
        $this->setShowTabs($tabs);
408
    }
409
410
    /**
411
     * Rename a form group.
412
     *
413
     * @param string $group        the old group name
414
     * @param string $tab          the tab the group belongs to
415
     * @param string $newGroupName the new group name
416
     *
417
     * @return self
418
     */
419
    public function renameFormGroup($group, $tab, $newGroupName)
420
    {
421
        $groups = $this->getFormGroups();
422
423
        // When the default tab is used, the tabname is not prepended to the index in the group array
424
        if ($tab !== 'default') {
425
            $group = $tab . '.' . $group;
426
        }
427
        $newGroup = ($tab !== 'default') ? $tab . '.' . $newGroupName : $newGroupName;
428
429
        if (isset($groups[$newGroup])) {
430
            throw new \Exception(sprintf('%s form group already exists.', $newGroup));
431
        }
432
        if (!array_key_exists($group, $groups)) {
433
            throw new \Exception(sprintf('form group « %s » doesn\'t exist.', $group));
434
        }
435
436
        $groups[$newGroup] = $groups[$group];
437
        $groups[$newGroup]['name'] = $newGroupName;
438
        unset($groups[$group]);
439
440
        $tabs = $this->getFormTabs();
441
        $key = array_search($group, $tabs[$tab]['groups']);
442
443
        if (false !== $key) {
444
            $tabs[$tab]['groups'][$key] = $newGroup;
445
        }
446
447
        $this->setFormTabs($tabs);
448
        $this->setFormGroups($groups);
449
450
        return $this;
451
    }
452
453
    /**
454
     * Removes tab in current form Mapper.
455
     *
456
     * @param string|array $tabNames name or array of names of tabs to be removed
457
     * @param FormMapper   $mapper   Sonata Admin form mapper
458
     */
459
    public function removeTab($tabNames, $mapper)
460
    {
461
        $currentTabs = $this->getFormTabs();
462
        foreach ($currentTabs as $k => $item) {
0 ignored issues
show
Bug introduced by
The expression $currentTabs 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...
463
            if (is_array($tabNames) && in_array($item['name'], $tabNames) || !is_array($tabNames) && $item['name'] === $tabNames) {
464
                foreach ($item['groups'] as $groupName) {
465
                    $this->removeAllFieldsFromFormGroup($groupName, $mapper);
466
                }
467
                unset($currentTabs[$k]);
468
            }
469
        }
470
        $this->setFormTabs($currentTabs);
471
    }
472
473
    /**
474
     * Removes all fields from form groups and remove them from mapper.
475
     *
476
     * @param string     $groupName Name of the group to remove
477
     * @param FormMapper $mapper    Sonata Admin form mapper
478
     */
479
    public function removeAllFieldsFromFormGroup($groupName, $mapper)
480
    {
481
        $formGroups = $this->getFormGroups();
482
        foreach ($formGroups as $name => $formGroup) {
0 ignored issues
show
Bug introduced by
The expression $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...
483
            if ($name === $groupName) {
484
                foreach ($formGroups[$name]['fields'] as $key => $field) {
485
                    $mapper->remove($key);
486
                }
487
            }
488
        }
489
    }
490
491
    public function jsonSerialize()
492
    {
493
        $propertiesToShow = [
494
            'baseRouteName',
495
            'baseRoutePattern',
496
            'extraTemplates',
497
            'listFieldDescriptions',
498
            'showFieldDescriptions',
499
            'formFieldDescriptions',
500
            'filterFieldDescriptions',
501
            'maxPerPage',
502
            'maxPageLinks',
503
            'classnameLabel',
504
            'translationDomain',
505
            'formOptions',
506
            'datagridValues',
507
            'perPageOptions',
508
            'pagerType',
509
            'code',
510
            'label',
511
            'routes',
512
            'subject',
513
            'children',
514
            'parent',
515
            'baseCodeRoute',
516
            'uniqid',
517
            'extensions',
518
            'class',
519
            'subClasses',
520
            'list',
521
            'show',
522
            'form',
523
            'filter',
524
            'formGroups',
525
            'formTabs',
526
            'showGroups',
527
            'showTabs',
528
            'managedCollections',
529
            'helperLinks',
530
            'titles',
531
        ];
532
533
        $properties = [];
534
        foreach ($this as $key => $value) {
535
            if (in_array($key, $propertiesToShow)) {
536
                $properties[$key] = $value;
537
            }
538
        }
539
540
        return $properties;
541
    }
542
543
    /**
544
     * {@inheritdoc}
545
     */
546
    public function prePersist($object)
547
    {
548
        parent::prePersist($object);
549
550
        $hasCodeGenerator = CodeGeneratorRegistry::hasGeneratorForClass(get_class($object));
551
        if ($hasCodeGenerator) {
552
            $accessor = PropertyAccess::createPropertyAccessor();
553
            foreach (CodeGeneratorRegistry::getCodeGenerators(get_class($object)) as $name => $generator) {
554
                $accessor->setValue($object, $name, $generator->generate($object));
555
            }
556
        }
557
    }
558
559
    public function getFlashManager()
560
    {
561
        return $this->getConfigurationPool()->getContainer()->get('sonata.core.flashmessage.manager');
562
    }
563
564
    /**
565
     * {@inheritdoc}
566
     */
567
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
568
    {
569
        parent::preBatchAction($actionName, $query, $idx, $allElements);
570
571
        if ($actionName === 'delete') {
572
            $cascadingRelationChecker = $this->getConfigurationPool()->getContainer()->get('blast_core.doctrine.orm.cascading_relation_checker');
573
574
            foreach ($idx as $id) {
575
                $entity = $this->getModelManager()->find($this->getClass(), $id);
576
577
                if ($entity !== null) {
578
                    $undeletableAssociations = $cascadingRelationChecker->beforeEntityDelete($entity, $idx);
579
580
                    if (count($undeletableAssociations) > 0) {
581
                        foreach ($undeletableAssociations as $key => $undeletableAssociation) {
582
                            $undeletableAssociations[$key] = $this->getConfigurationPool()->getContainer()->get('translator')->trans('blast.doctrine_relations.' . $undeletableAssociation, [], 'messages');
583
                        }
584
585
                        $errorMessage = 'Cannot delete "%entity%" because it has remaining relation(s) %relations%';
586
587
                        $message = $this->getTranslator()->trans(
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...tAdmin::getTranslator() has been deprecated with message: since 3.9, to be removed with 4.0

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...
588
                            $errorMessage,
589
                            [
590
                                '%relations%' => trim(implode(', ', $undeletableAssociations)),
591
                                '%entity%'    => (string) $entity,
592
                            ],
593
                            'SonataCoreBundle'
594
                        );
595
596
                        $this->getConfigurationPool()->getContainer()->get('session')->getFlashBag()->add('warning', $message);
597
                    }
598
                }
599
            }
600
        }
601
    }
602
}
603