Completed
Pull Request — 3.x (#6276)
by
unknown
03:07
created

BaseGroupMenuProvider   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 157
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 5
dl 0
loc 157
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 24 3
A doHas() 0 4 1
B doGet() 0 34 9
C canGenerateMenuItem() 0 41 12
A generateMenuItem() 0 27 4
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Menu\Provider;
15
16
use Knp\Menu\FactoryInterface;
17
use Knp\Menu\ItemInterface;
18
use Knp\Menu\Provider\MenuProviderInterface;
19
use Sonata\AdminBundle\Admin\Pool;
20
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
21
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
22
23
/**
24
 * NEXT_MAJOR: Copy all methods to GroupMenuProvider and remove this class when dropping support of knplabs/knp-menu 2.x.
25
 *
26
 * @internal
27
 */
28
abstract class BaseGroupMenuProvider implements MenuProviderInterface
29
{
30
    /**
31
     * @var FactoryInterface
32
     */
33
    private $menuFactory;
34
35
    /**
36
     * @var Pool
37
     */
38
    private $pool;
39
40
    /**
41
     * @var AuthorizationCheckerInterface
42
     */
43
    private $checker;
44
45
    /**
46
     * NEXT_MAJOR: Remove default value null of $checker.
47
     *
48
     * @param AuthorizationCheckerInterface|null $checker
49
     */
50
    public function __construct(FactoryInterface $menuFactory, Pool $pool, $checker = null)
51
    {
52
        $this->menuFactory = $menuFactory;
53
        $this->pool = $pool;
54
55
        /*
56
         * NEXT_MAJOR: Remove this if blocks.
57
         * NEXT_MAJOR: Move AuthorizationCheckerInterface check to method signature.
58
         */
59
        if (null === $checker) {
60
            @trigger_error(sprintf(
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...
61
                'Passing no 3rd argument is deprecated since version 3.10 and will be mandatory in 4.0.'
62
                .' Pass %s as 3rd argument.',
63
                AuthorizationCheckerInterface::class
64
            ), E_USER_DEPRECATED);
65
        } elseif (!$checker instanceof AuthorizationCheckerInterface) {
66
            throw new \InvalidArgumentException(sprintf(
67
                'Argument 3 must be an instance of %s',
68
                AuthorizationCheckerInterface::class
69
            ));
70
        }
71
72
        $this->checker = $checker;
73
    }
74
75
    public function doHas(string $name, array $options = []): bool
0 ignored issues
show
Unused Code introduced by
The parameter $options 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...
76
    {
77
        return 'sonata_group_menu' === $name;
78
    }
79
80
    protected function doGet(string $name, array $options = []): ItemInterface
0 ignored issues
show
Unused Code introduced by
The parameter $name 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...
81
    {
82
        /**
83
         * @var array{ label: string, label_catalogue: string, icon: string, on_top?: bool, keep_open: bool, provider: string, items: list }
84
         */
85
        $group = $options['group'];
86
87
        $menuItem = $this->menuFactory->createItem($options['name']);
88
89
        if (!\array_key_exists('on_top', $group) || false === $group['on_top']) {
90
            foreach ($group['items'] as $item) {
91
                if ($this->canGenerateMenuItem($item, $group)) {
92
                    $menuItem->addChild($this->generateMenuItem($item, $group));
93
                }
94
            }
95
96
            if (false === $menuItem->hasChildren()) {
97
                $menuItem->setDisplay(false);
98
            } elseif (!empty($group['keep_open'])) {
99
                $menuItem->setAttribute('class', 'keep-open');
100
                $menuItem->setExtra('keep_open', $group['keep_open']);
101
            }
102
        } elseif (1 === \count($group['items'])) {
103
            if ($this->canGenerateMenuItem($group['items'][0], $group)) {
104
                $menuItem = $this->generateMenuItem($group['items'][0], $group);
105
                $menuItem->setExtra('on_top', $group['on_top']);
106
            } else {
107
                $menuItem->setDisplay(false);
108
            }
109
        }
110
        $menuItem->setLabel($group['label']);
111
112
        return $menuItem;
113
    }
114
115
    private function canGenerateMenuItem(array $item, array $group): bool
116
    {
117
        if (isset($item['admin']) && !empty($item['admin'])) {
118
            $admin = $this->pool->getInstance($item['admin']);
119
120
            // skip menu item if no `list` url is available or user doesn't have the LIST access rights
121
            return $admin->hasRoute('list') && $admin->hasAccess('list');
122
        }
123
124
        //NEXT_MAJOR: Remove if statement of null checker.
125
        if (null === $this->checker) {
126
            return true;
127
        }
128
129
        // Making the checker behave affirmatively even if it's globally unanimous
130
        // Still must be granted unanimously to group and item
131
132
        $isItemGranted = true;
133
        if (!empty($item['roles'])) {
134
            $isItemGranted = false;
135
            foreach ($item['roles'] as $role) {
136
                if ($this->checker->isGranted($role)) {
137
                    $isItemGranted = true;
138
                    break;
139
                }
140
            }
141
        }
142
143
        $isGroupGranted = true;
144
        if (!empty($group['roles'])) {
145
            $isGroupGranted = false;
146
            foreach ($group['roles'] as $role) {
147
                if ($this->checker->isGranted($role)) {
148
                    $isGroupGranted = true;
149
                    break;
150
                }
151
            }
152
        }
153
154
        return $isItemGranted && $isGroupGranted;
155
    }
156
157
    private function generateMenuItem(array $item, array $group): ItemInterface
158
    {
159
        if (isset($item['admin']) && !empty($item['admin'])) {
160
            $admin = $this->pool->getInstance($item['admin']);
161
162
            $options = $admin->generateMenuUrl(
163
                'list',
164
                [],
165
                $item['route_absolute'] ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH
166
            );
167
            $options['extras'] = [
168
                'label_catalogue' => $admin->getTranslationDomain(),
169
                'admin' => $admin,
170
            ];
171
172
            return $this->menuFactory->createItem($admin->getLabel(), $options);
173
        }
174
175
        return $this->menuFactory->createItem($item['label'], [
176
            'route' => $item['route'],
177
            'routeParameters' => $item['route_params'],
178
            'routeAbsolute' => $item['route_absolute'],
179
            'extras' => [
180
                'label_catalogue' => $group['label_catalogue'],
181
            ],
182
        ]);
183
    }
184
}
185