Passed
Push — master ( c62529...5628da )
by Florian
03:24
created

Builder::findItem()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 21
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 6

Importance

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