Passed
Pull Request — master (#1151)
by Florian
03:01
created

Builder::addItem()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 15
nc 4
nop 3
dl 0
loc 29
ccs 0
cts 15
cp 0
crap 30
rs 9.4555
c 0
b 0
f 0
1
<?php
2
3
namespace JeroenNoten\LaravelAdminLte\Menu;
4
5
use Illuminate\Support\Arr;
6
use JeroenNoten\LaravelAdminLte\Helpers\MenuItemHelper;
7
8
class Builder
9
{
10
    protected const ADD_AFTER = 0;
11
    protected const ADD_BEFORE = 1;
12
    protected const ADD_INSIDE = 2;
13
14
    /**
15
     * The set of menu items.
16
     *
17
     * @var array
18
     */
19
    public $menu = [];
20
21
    /**
22
     * The set of filters applied to menu items.
23
     *
24
     * @var array
25
     */
26
    private $filters;
27
28
    /**
29
     * Constructor.
30
     *
31
     * @param  array  $filters
32
     */
33
    public function __construct(array $filters = [])
34
    {
35
        $this->filters = $filters;
36
    }
37
38
    /**
39
     * Add new items at the end of the menu.
40
     *
41
     * @param  mixed  $newItems  Items to be added
42
     */
43
    public function add(...$newItems)
44
    {
45
        $items = $this->transformItems($newItems);
46
47
        if (! empty($items)) {
48
            array_push($this->menu, ...$items);
49
        }
50
    }
51
52
    /**
53
     * Add new items after a specific menu item.
54
     *
55
     * @param  mixed  $itemKey  The key that represents the specific menu item
56
     * @param  mixed  $newItems  Items to be added
57
     */
58
    public function addAfter($itemKey, ...$newItems)
59
    {
60
        $this->addItem($itemKey, self::ADD_AFTER, ...$newItems);
61
    }
62
63
    /**
64
     * Add new items before a specific menu item.
65
     *
66
     * @param  mixed  $itemKey  The key that represents the specific menu item
67
     * @param  mixed  $newItems  Items to be added
68
     */
69
    public function addBefore($itemKey, ...$newItems)
70
    {
71
        $this->addItem($itemKey, self::ADD_BEFORE, ...$newItems);
72
    }
73
74
    /**
75
     * Add new submenu items inside a specific menu item.
76
     *
77
     * @param  mixed  $itemKey  The key that represents the specific menu item
78
     * @param  mixed  $newItems  Items to be added
79
     */
80
    public function addIn($itemKey, ...$newItems)
81
    {
82
        $this->addItem($itemKey, self::ADD_INSIDE, ...$newItems);
83
    }
84
85
    /**
86
     * Remove a specific menu item.
87
     *
88
     * @param  mixed  $itemKey  The key of the menu item to remove
89
     */
90
    public function remove($itemKey)
91
    {
92
        // Find the specific menu item. Return if not found.
93
94
        if (! ($itemPath = $this->findItem($itemKey, $this->menu))) {
95
            return;
96
        }
97
98
        // Remove the item.
99
100
        Arr::forget($this->menu, implode('.', $itemPath));
101
102
        // Normalize the menu (remove holes in the numeric indexes).
103
104
        $holedArrPath = implode('.', array_slice($itemPath, 0, -1)) ?: null;
105
        $holedArr = Arr::get($this->menu, $holedArrPath, $this->menu);
106
        Arr::set($this->menu, $holedArrPath, array_values($holedArr));
107
    }
108
109
    /**
110
     * Check if exists a menu item with the specified key.
111
     *
112
     * @param  mixed  $itemKey  The key of the menu item to check for
113
     * @return bool
114
     */
115
    public function itemKeyExists($itemKey)
116
    {
117
        return (bool) $this->findItem($itemKey, $this->menu);
118
    }
119
120
    /**
121
     * Transform the items by applying the filters.
122
     *
123
     * @param  array  $items  An array with items to be transformed
124
     * @return array Array with the new transformed items
125
     */
126
    protected function transformItems($items)
127
    {
128
        return array_filter(
129
            array_map([$this, 'applyFilters'], $items),
130
            [MenuItemHelper::class, 'isAllowed']
131
        );
132
    }
133
134
    /**
135
     * Find a menu item by the item key and return the path to it.
136
     *
137
     * @param  mixed  $itemKey  The key of the item to find
138
     * @param  array  $items  The array to look up for the item
139
     * @return mixed Array with the path sequence, or empty array if not found
140
     */
141
    protected function findItem($itemKey, $items)
142
    {
143
        // Look up on all the items.
144
145
        foreach ($items as $key => $item) {
146
            if (isset($item['key']) && $item['key'] === $itemKey) {
147
                return [$key];
148
            } elseif (MenuItemHelper::isSubmenu($item)) {
149
150
                // Do the recursive call to search on submenu. If we found the
151
                // item, merge the path with the current one.
152
153
                if ($subPath = $this->findItem($itemKey, $item['submenu'])) {
154
                    return array_merge([$key, 'submenu'], $subPath);
155
                }
156
            }
157
        }
158
159
        // Return empty array when the item is not found.
160
161
        return [];
162
    }
163
164
    /**
165
     * Apply all the available filters to a menu item.
166
     *
167
     * @param  mixed  $item  A menu item
168
     * @return mixed A new item with all the filters applied
169
     */
170
    protected function applyFilters($item)
171
    {
172
        // Filters are only applied to array type menu items.
173
174
        if (! is_array($item)) {
175
            return $item;
176
        }
177
178
        // If the item is a submenu, transform all the submenu items first.
179
        // These items need to be transformed first because some of the submenu
180
        // filters (like the ActiveFilter) depends on these results.
181
182
        if (MenuItemHelper::isSubmenu($item)) {
183
            $item['submenu'] = $this->transformItems($item['submenu']);
184
        }
185
186
        // Now, apply all the filters on the item.
187
188
        foreach ($this->filters as $filter) {
189
190
            // If the item is not allowed to be shown, there is no sense to
191
            // continue applying the filters.
192
193
            if (! MenuItemHelper::isAllowed($item)) {
194
                return $item;
195
            }
196
197
            $item = $filter->transform($item);
198
        }
199
200
        return $item;
201
    }
202
203
    /**
204
     * Add new items to the menu in a particular place, relative to a
205
     * specific menu item.
206
     *
207
     * @param  mixed  $itemKey  The key that represents the specific menu item
208
     * @param  int  $where  Where to add the new items
209
     * @param  mixed  $items  Items to be added
210
     */
211
    protected function addItem($itemKey, $where, ...$items)
212
    {
213
        // Find the specific menu item. Return if not found.
214
215
        if (! ($itemPath = $this->findItem($itemKey, $this->menu))) {
216
            return;
217
        }
218
219
        // Get the target array and add the new items there.
220
221
        $itemKeyIdx = end($itemPath);
222
        reset($itemPath);
223
224
        if ($where === self::ADD_INSIDE) {
225
            $targetPath = implode('.', array_merge($itemPath, ['submenu']));
226
            $targetArr = Arr::get($this->menu, $targetPath, []);
227
            array_push($targetArr, ...$items);
228
        } else {
229
            $targetPath = implode('.', array_slice($itemPath, 0, -1)) ?: null;
230
            $targetArr = Arr::get($this->menu, $targetPath, $this->menu);
231
            $offset = ($where === self::ADD_AFTER) ? 1 : 0;
232
            array_splice($targetArr, $itemKeyIdx + $offset, 0, $items);
233
        }
234
235
        Arr::set($this->menu, $targetPath, $targetArr);
236
237
        // Apply the filters because the menu now have new items.
238
239
        $this->menu = $this->transformItems($this->menu);
240
    }
241
}
242