CoreAdmin::preBatchAction()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 35
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 35
rs 8.439
cc 6
eloc 18
nc 6
nop 4
1
<?php
2
3
/*
4
 * This file is part of the Blast Project package.
5
 *
6
 * Copyright (C) 2015-2017 Libre Informatique
7
 *
8
 * This file is licenced under the GNU LGPL v3.
9
 * For the full copyright and license information, please view the LICENSE.md
10
 * file that was distributed with this source code.
11
 */
12
13
namespace Blast\CoreBundle\Admin;
14
15
use Sonata\AdminBundle\Datagrid\DatagridMapper;
16
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
17
use Sonata\AdminBundle\Datagrid\ListMapper;
18
use Sonata\AdminBundle\Form\FormMapper;
19
use Sonata\AdminBundle\Mapper\BaseMapper;
20
use Sonata\AdminBundle\Show\ShowMapper;
21
use Sonata\AdminBundle\Route\RouteCollection;
22
use Sonata\AdminBundle\Admin\AbstractAdmin as SonataAdmin;
23
use Sonata\DoctrineORMAdminBundle\Admin\FieldDescription;
24
use Blast\CoreBundle\Tools\Reflection\ClassAnalyzer;
25
use Blast\CoreBundle\Admin\Traits\CollectionsManager;
26
use Blast\CoreBundle\Admin\Traits\Mapper;
27
use Blast\CoreBundle\Admin\Traits\Templates;
28
use Blast\CoreBundle\Admin\Traits\PreEvents;
29
use Blast\CoreBundle\Admin\Traits\ManyToManyManager;
30
use Blast\CoreBundle\Admin\Traits\Actions;
31
use Blast\CoreBundle\Admin\Traits\ListActions;
32
use Blast\CoreBundle\CodeGenerator\CodeGeneratorRegistry;
33
use Blast\CoreBundle\Translator\LibrinfoLabelTranslatorStrategy;
34
use Symfony\Component\PropertyAccess\PropertyAccess;
35
36
abstract class CoreAdmin extends SonataAdmin implements \JsonSerializable
37
{
38
    use CollectionsManager,
39
        ManyToManyManager,
40
        Mapper,
41
        Templates,
42
        PreEvents,
43
        Actions,
44
        ListActions
45
    ;
46
47
    protected $extraTemplates = [];
48
49
    public function configure()
50
    {
51
        parent::configure();
52
53
        /* Default Translation Strategy if not set as admin service tags */
54
        /* @todo : find if it is a good idea or not */
55
        if (!($this->getLabelTranslatorStrategy() instanceof LibrinfoLabelTranslatorStrategy)) {
56
            $this->setLabelTranslatorStrategy(new LibrinfoLabelTranslatorStrategy());
57
        }
58
        /* Should always be */
59
        if ($this->getLabelTranslatorStrategy() instanceof LibrinfoLabelTranslatorStrategy) {
60
            $this->getLabelTranslatorStrategy()->setFix($this->getClassnameLabel());
61
        }
62
63
        /* @todo: apply TranslatorStrategy to form_tab and form_group and show_tab and
64
           ... warning it may impact code as it used in some postConfigureFormFields */
65
    }
66
67
    /**
68
     * Configure routes for list actions.
69
     *
70
     * @param RouteCollection $collection
71
     */
72
    protected function configureRoutes(RouteCollection $collection)
73
    {
74
        parent::configureRoutes($collection);
75
        $collection->add('duplicate', $this->getRouterIdParameter() . '/duplicate');
76
        $collection->add('generateEntityCode');
77
78
        /* Needed or not needed ...
79
         * in sonata-project/admin-bundle/Controller/CRUDController.php
80
         * the batchAction method
81
         * throw exception if the http method is not POST
82
        */
83
        if ($collection->get('batch')) {
84
            $collection->get('batch')->setMethods(['POST']);
85
        }
86
    }
87
88 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...
89
    {
90
        $configuredBaseRoute = $this->getBaseRouteMapping();
91
92
        if (count($configuredBaseRoute) > 0) {
93
            $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...
94
            if (isset($configuredBaseRoute['name']) && $this->baseRouteName === null) {
95
                $this->baseRouteName = $configuredBaseRoute['name'];
96
            }
97
        }
98
99
        return parent::getBaseRouteName();
100
    }
101
102 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...
103
    {
104
        $configuredBaseRoute = $this->getBaseRouteMapping();
105
106
        if (count($configuredBaseRoute) > 0) {
107
            $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...
108
            if (isset($configuredBaseRoute['pattern']) && $this->baseRoutePattern === null) {
109
                $this->baseRoutePattern = $configuredBaseRoute['pattern'];
110
            }
111
        }
112
113
        return parent::getBaseRoutePattern();
114
    }
115
116
    public function getFormTheme()
117
    {
118
        return array_merge($this->formTheme, $this->getFormThemeMapping());
119
    }
120
121
    /**
122
     * @param DatagridMapper $mapper
123
     */
124
    protected function configureDatagridFilters(DatagridMapper $mapper)
125
    {
126
        if (!$this->configureMapper($mapper)) {
127
            $this->fallbackConfiguration($mapper, __FUNCTION__);
128
        }
129
    }
130
131
    /**
132
     * @param ListMapper $mapper
133
     */
134
    protected function configureListFields(ListMapper $mapper)
135
    {
136
        if (!$this->configureMapper($mapper)) {
137
            $this->fallbackConfiguration($mapper, __FUNCTION__);
138
        }
139
    }
140
141
    /**
142
     * @param FormMapper $mapper
143
     */
144
    protected function configureFormFields(FormMapper $mapper)
145
    {
146
        if (!$this->configureMapper($mapper)) {
147
            $this->fallbackConfiguration($mapper, __FUNCTION__);
148
        }
149
    }
150
151
    /**
152
     * @param ShowMapper $mapper
153
     */
154
    protected function configureShowFields(ShowMapper $mapper)
155
    {
156
        if (!$this->configureMapper($mapper)) {
157
            $this->fallbackConfiguration($mapper, __FUNCTION__);
158
        }
159
    }
160
161
    /**
162
     * @param BaseMapper $mapper
163
     */
164
    protected function fixShowRoutes(BaseMapper $mapper)
165
    {
166
        foreach (['getShow', 'getList'] as $fct) {
167
            foreach ($this->$fct()->getElements() as $field) {
168
                if ($field instanceof FieldDescription) {
169
                    $options = $field->getOptions();
170
                    if ($options['route']['name'] != 'edit') {
171
                        continue;
172
                    }
173
174
                    $options['route']['name'] = 'show';
175
                    $field->setOptions($options);
176
                }
177
            }
178
        }
179
180
        return $this;
181
    }
182
183
    protected function getCurrentComposition()
184
    {
185
        // traits of the current Entity
186
        $classes = ClassAnalyzer::getTraits($this->getClass());
187
        // inheritance of the current Entity
188
        foreach (array_reverse([$this->getClass()] + class_parents($this->getClass())) as $class) {
189
            $classes[] = $class;
190
        }
191
        // inheritance of the current Admin
192
        foreach (array_reverse([$this->getOriginalClass()] + $this->getParentClasses()) as $admin) {
193
            $classes[] = $admin;
194
        }
195
196
        return $classes;
197
    }
198
199
    private function fallbackConfiguration(BaseMapper $mapper, $function)
200
    {
201
        // fallback
202
        $rm = new \ReflectionMethod($this->getParentClass(), $function);
203
        if ($rm->class == $this->getParentClass()) {
204
            $this->configureFields($function, $mapper, $this->getParentClass());
205
        }
206
    }
207
208
    /**
209
     * Returns the level of depth of an array.
210
     *
211
     * @param array $array
212
     * @param int   $level : do not use, just used for recursivity
213
     *
214
     * @return int : depth
215
     */
216
    private static function arrayDepth($array, $level = 0)
217
    {
218
        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...
219
            return $level;
220
        }
221
222
        if (!is_array($array)) {
223
            return $level;
224
        }
225
226
        ++$level;
227
        foreach ($array as $key => $value) {
228
            if (is_array($value)) {
229
                $level = $level < self::arrayDepth($value, $level) ? self::arrayDepth($value, $level) : $level;
230
            }
231
        }
232
233
        return $level;
234
    }
235
236
    protected function getOriginalClass()
237
    {
238
        return get_called_class();
239
    }
240
241
    protected function getParentClasses()
242
    {
243
        return class_parents($this->getOriginalClass());
244
    }
245
246
    protected function getParentClass()
247
    {
248
        return get_parent_class($this->getOriginalClass());
249
    }
250
251
    protected function getGrandParentClass()
252
    {
253
        return get_parent_class(get_parent_class($this->getOriginalClass()));
254
    }
255
256
    /**
257
     * @param string $view     'list', 'show', 'form', etc
258
     * @param string $template template name
259
     */
260
    public function addExtraTemplate($view, $template)
261
    {
262
        if (empty($this->extraTemplates[$view])) {
263
            $this->extraTemplates[$view] = [];
264
        }
265
        if (!in_array($template, $this->extraTemplates[$view])) {
266
            $this->extraTemplates[$view][] = $template;
267
        }
268
    }
269
270
    /**
271
     * @param string $view 'list', 'show', 'form', etc
272
     *
273
     * @return array array of template names
274
     */
275
    public function getExtraTemplates($view)
276
    {
277
        if (empty($this->extraTemplates[$view])) {
278
            $this->extraTemplates[$view] = [];
279
        }
280
281
        return $this->extraTemplates[$view];
282
    }
283
284
    /**
285
     * @param string $view 'list', 'show', 'form', etc
286
     * @param array  $link link (array keys should be: 'label', 'url', 'class', 'title')
287
     */
288
    public function addHelperLink($view, $link)
289
    {
290
        if (empty($this->helperLinks[$view])) {
291
            $this->helperLinks[$view] = [];
292
        }
293
294
        // Do not add links without URL
295
        if (empty($link['url'])) {
296
            return;
297
        }
298
299
        // Do not add two links with the same URL
300
        foreach ($this->helperLinks[$view] as $l) {
301
            if ($l['url'] == $link['url']) {
302
                return;
303
            }
304
        }
305
306
        $this->helperLinks[$view][] = $link;
307
    }
308
309
    /**
310
     * @param string $view 'list', 'show', 'form', etc
311
     *
312
     * @return array array of links (each link is an array with keys 'label', 'url', 'class' and 'title')
313
     */
314
    public function getHelperLinks($view)
315
    {
316
        if (empty($this->helperLinks[$view])) {
317
            $this->helperLinks[$view] = [];
318
        }
319
320
        return $this->helperLinks[$view];
321
    }
322
323
    /**
324
     * Checks if a Bundle is installed.
325
     *
326
     * @param string $bundle Bundle name or class FQN
327
     */
328
    public function bundleExists($bundle)
329
    {
330
        $kernelBundles = $this->getConfigurationPool()->getContainer()->getParameter('kernel.bundles');
331
        if (array_key_exists($bundle, $kernelBundles)) {
332
            return true;
333
        }
334
        if (in_array($bundle, $kernelBundles)) {
335
            return true;
336
        }
337
338
        return false;
339
    }
340
341
    /**
342
     * Rename a form tab after form fields have been configured.
343
     *
344
     * TODO: groups of the renamed tab are still prefixed with the old tab name
345
     *
346
     * @param type $tabName    the name of the tab to be renamed
347
     * @param type $newTabName the new name for the tab
348
     */
349 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...
350
    {
351
        $tabs = $this->getFormTabs();
352
353
        if (!$tabs) {
354
            return;
355
        }
356
357
        if (!isset($tabs[$tabName])) {
358
            throw new \Exception(sprintf('Tab %s does not exist.', $tabName));
359
        }
360
        if (isset($tabs[$newTabName])) {
361
            return;
362
        }
363
364
        if ($keepOrder) {
365
            $keys = array_keys($tabs);
366
            $keys[array_search($tabName, $keys)] = $newTabName;
367
            $tabs = array_combine($keys, $tabs);
368
        } else {
369
            $tabs[$newTabName] = $tabs[$tabName];
370
            unset($tabs[$tabName]);
371
        }
372
373
        $this->setFormTabs($tabs);
374
    }
375
376
    /**
377
     * Rename a show tab after show fields have been configured.
378
     *
379
     * TODO: groups of the renamed tab are still prefixed with the old tab name
380
     *
381
     * @param type $tabName    the name of the tab to be renamed
382
     * @param type $newTabName the new name for the tab
383
     */
384 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...
385
    {
386
        $tabs = $this->getShowTabs();
387
388
        if (!$tabs) {
389
            return;
390
        }
391
392
        if (!isset($tabs[$tabName])) {
393
            throw new \Exception(sprintf('Tab %s does not exist.', $tabName));
394
        }
395
        if (isset($tabs[$newTabName])) {
396
            return;
397
        }
398
399
        if ($keepOrder) {
400
            $keys = array_keys($tabs);
401
            $keys[array_search($tabName, $keys)] = $newTabName;
402
            $tabs = array_combine($keys, $tabs);
403
        } else {
404
            $tabs[$newTabName] = $tabs[$tabName];
405
            unset($tabs[$tabName]);
406
        }
407
408
        $this->setShowTabs($tabs);
409
    }
410
411
    /**
412
     * Rename a form group.
413
     *
414
     * @param string $group        the old group name
415
     * @param string $tab          the tab the group belongs to
416
     * @param string $newGroupName the new group name
417
     *
418
     * @return self
419
     */
420
    public function renameFormGroup($group, $tab, $newGroupName)
421
    {
422
        $groups = $this->getFormGroups();
423
424
        // When the default tab is used, the tabname is not prepended to the index in the group array
425
        if ($tab !== 'default') {
426
            $group = $tab . '.' . $group;
427
        }
428
        $newGroup = ($tab !== 'default') ? $tab . '.' . $newGroupName : $newGroupName;
429
430
        if (isset($groups[$newGroup])) {
431
            throw new \Exception(sprintf('%s form group already exists.', $newGroup));
432
        }
433
        if (!array_key_exists($group, $groups)) {
434
            throw new \Exception(sprintf('form group « %s » doesn\'t exist.', $group));
435
        }
436
437
        $groups[$newGroup] = $groups[$group];
438
        $groups[$newGroup]['name'] = $newGroupName;
439
        unset($groups[$group]);
440
441
        $tabs = $this->getFormTabs();
442
        $key = array_search($group, $tabs[$tab]['groups']);
443
444
        if (false !== $key) {
445
            $tabs[$tab]['groups'][$key] = $newGroup;
446
        }
447
448
        $this->setFormTabs($tabs);
449
        $this->setFormGroups($groups);
450
451
        return $this;
452
    }
453
454
    /**
455
     * Removes tab in current form Mapper.
456
     *
457
     * @param string|array $tabNames name or array of names of tabs to be removed
458
     * @param FormMapper   $mapper   Sonata Admin form mapper
459
     */
460
    public function removeTab($tabNames, $mapper)
461
    {
462
        $currentTabs = $this->getFormTabs();
463
        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...
464
            if (is_array($tabNames) && in_array($item['name'], $tabNames) || !is_array($tabNames) && $item['name'] === $tabNames) {
465
                foreach ($item['groups'] as $groupName) {
466
                    $this->removeAllFieldsFromFormGroup($groupName, $mapper);
467
                }
468
                unset($currentTabs[$k]);
469
            }
470
        }
471
        $this->setFormTabs($currentTabs);
472
    }
473
474
    /**
475
     * Removes all fields from form groups and remove them from mapper.
476
     *
477
     * @param string     $groupName Name of the group to remove
478
     * @param FormMapper $mapper    Sonata Admin form mapper
479
     */
480
    public function removeAllFieldsFromFormGroup($groupName, $mapper)
481
    {
482
        $formGroups = $this->getFormGroups();
483
        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...
484
            if ($name === $groupName) {
485
                foreach ($formGroups[$name]['fields'] as $key => $field) {
486
                    $mapper->remove($key);
487
                }
488
            }
489
        }
490
    }
491
492
    public function jsonSerialize()
493
    {
494
        $propertiesToShow = [
495
            'baseRouteName',
496
            'baseRoutePattern',
497
            'extraTemplates',
498
            'listFieldDescriptions',
499
            'showFieldDescriptions',
500
            'formFieldDescriptions',
501
            'filterFieldDescriptions',
502
            'maxPerPage',
503
            'maxPageLinks',
504
            'classnameLabel',
505
            'translationDomain',
506
            'formOptions',
507
            'datagridValues',
508
            'perPageOptions',
509
            'pagerType',
510
            'code',
511
            'label',
512
            'routes',
513
            'subject',
514
            'children',
515
            'parent',
516
            'baseCodeRoute',
517
            'uniqid',
518
            'extensions',
519
            'class',
520
            'subClasses',
521
            'list',
522
            'show',
523
            'form',
524
            'filter',
525
            'formGroups',
526
            'formTabs',
527
            'showGroups',
528
            'showTabs',
529
            'managedCollections',
530
            'helperLinks',
531
            'titles',
532
        ];
533
534
        $properties = [];
535
        foreach ($this as $key => $value) {
536
            if (in_array($key, $propertiesToShow)) {
537
                $properties[$key] = $value;
538
            }
539
        }
540
541
        return $properties;
542
    }
543
544
    /**
545
     * {@inheritdoc}
546
     */
547
    public function prePersist($object)
548
    {
549
        parent::prePersist($object);
550
551
        $hasCodeGenerator = CodeGeneratorRegistry::hasGeneratorForClass(get_class($object));
552
        if ($hasCodeGenerator) {
553
            $accessor = PropertyAccess::createPropertyAccessor();
554
            foreach (CodeGeneratorRegistry::getCodeGenerators(get_class($object)) as $name => $generator) {
555
                $accessor->setValue($object, $name, $generator->generate($object));
556
            }
557
        }
558
    }
559
560
    public function getFlashManager()
561
    {
562
        return $this->getConfigurationPool()->getContainer()->get('sonata.core.flashmessage.manager');
563
    }
564
565
    /**
566
     * {@inheritdoc}
567
     */
568
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
569
    {
570
        parent::preBatchAction($actionName, $query, $idx, $allElements);
571
572
        if ($actionName === 'delete') {
573
            $cascadingRelationChecker = $this->getConfigurationPool()->getContainer()->get('blast_core.doctrine.orm.cascading_relation_checker');
574
575
            foreach ($idx as $id) {
576
                $entity = $this->getModelManager()->find($this->getClass(), $id);
577
578
                if ($entity !== null) {
579
                    $undeletableAssociations = $cascadingRelationChecker->beforeEntityDelete($entity, $idx);
580
581
                    if (count($undeletableAssociations) > 0) {
582
                        foreach ($undeletableAssociations as $key => $undeletableAssociation) {
583
                            $undeletableAssociations[$key] = $this->getConfigurationPool()->getContainer()->get('translator')->trans('blast.doctrine_relations.' . $undeletableAssociation, [], 'messages');
584
                        }
585
586
                        $errorMessage = 'Cannot delete "%entity%" because it has remaining relation(s) %relations%';
587
588
                        $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...
589
                            $errorMessage,
590
                            [
591
                                '%relations%' => trim(implode(', ', $undeletableAssociations)),
592
                                '%entity%'    => (string) $entity,
593
                            ],
594
                            'SonataCoreBundle'
595
                        );
596
597
                        $this->getConfigurationPool()->getContainer()->get('session')->getFlashBag()->add('warning', $message);
598
                    }
599
                }
600
            }
601
        }
602
    }
603
}
604