Passed
Push — master ( 73cd93...7a270d )
by Alexander
30:15 queued 27:55
created

Nav::renderItems()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 6
c 1
b 0
f 0
nc 3
nop 0
dl 0
loc 13
ccs 7
cts 7
cp 1
crap 4
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bulma;
6
7
use Yiisoft\Arrays\ArrayHelper;
8
use Yiisoft\Html\Html;
9
use Yiisoft\Widget\Exception\InvalidConfigException;
10
11
use function implode;
12
use function is_array;
13
14
final class Nav extends Widget
15
{
16
    private bool $activateItems = true;
17
    private bool $activateParents = false;
18
    private string $currentPath = '';
19
    private bool $dropdown = false;
20
    private bool $encodeLabels = true;
21
    private array $items = [];
22
23 19
    protected function run(): string
24
    {
25 19
        $html = $this->renderItems();
26
27 17
        if ($this->dropdown) {
28 9
            $html .= Html::endTag('div');
29
        }
30
31 17
        return $html;
32
    }
33
34
    /**
35
     * Whether to automatically activate items according to whether their currentPath matches the currently requested.
36
     *
37
     * @param bool $value
38
     *
39
     * @return self
40
     *
41
     * {@see isItemActive}
42
     */
43 2
    public function activateItems(bool $value): self
44
    {
45 2
        $this->activateItems = $value;
46 2
        return $this;
47
    }
48
49
    /**
50
     * Whether to activate parent menu items when one of the corresponding child menu items is active.
51
     *
52
     * @param bool $value
53
     *
54
     * @return self
55
     */
56 1
    public function activateParents(bool $value): self
57
    {
58 1
        $this->activateParents = $value;
59 1
        return $this;
60
    }
61
62
    /**
63
     * Allows you to assign the current path of the url from request controller.
64
     *
65
     * @param string $value
66
     *
67
     * @return self
68
     */
69 2
    public function currentPath(string $value): self
70
    {
71 2
        $this->currentPath = $value;
72 2
        return $this;
73
    }
74
75
    /**
76
     * Whether the nav items labels should be HTML-encoded.
77
     *
78
     * @param bool $value
79
     *
80
     * @return self
81
     */
82 8
    public function encodeLabels(bool $value): self
83
    {
84 8
        $this->encodeLabels = $value;
85 8
        return $this;
86
    }
87
88
    /**
89
     * List of items in the nav widget. Each array element represents a single  menu item which can be either a string
90
     * or an array with the following structure:
91
     *
92
     * - label: string, required, the nav item label.
93
     * - url: optional, the item's URL. Defaults to "#".
94
     * - visible: bool, optional, whether this menu item is visible. Defaults to true.
95
     * - linkOptions: array, optional, the HTML attributes of the item's link.
96
     * - options: array, optional, the HTML attributes of the item container (LI).
97
     * - active: bool, optional, whether the item should be on active state or not.
98
     * - dropdownOptions: array, optional, the HTML options that will passed to the {@see Dropdown} widget.
99
     * - items: array|string, optional, the configuration array for creating a {@see Dropdown} widget, or a string
100
     *   representing the dropdown menu.
101
     * - encode: bool, optional, whether the label will be HTML-encoded. If set, supersedes the $encodeLabels option for
102
     *   only this item.
103
     *
104
     * If a menu item is a string, it will be rendered directly without HTML encoding.
105
     *
106
     * @param array $value
107
     * @return Nav
108
     */
109 19
    public function items(array $value): self
110
    {
111 19
        $this->items = $value;
112 19
        return $this;
113
    }
114
115
    /**
116
     * Renders the given items as a dropdown.
117
     *
118
     * This method is called to create sub-menus.
119
     *
120
     * @param array $items the given items. Please refer to {@see Dropdown::items} for the array structure.
121
     * @param array $parentItem the parent item information. Please refer to {@see items} for the structure of this
122
     * array.
123
     *
124
     * @return string the rendering result.
125
     */
126 10
    private function renderDropdown(array $items, array $parentItem): string
127
    {
128 10
        return Dropdown::widget()
129 10
            ->cssDivider('navbar-divider')
0 ignored issues
show
Bug introduced by
The method cssDivider() does not exist on Yiisoft\Widget\Widget. It seems like you code against a sub-type of Yiisoft\Widget\Widget such as Yiisoft\Yii\Bulma\Dropdown. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

129
            ->/** @scrutinizer ignore-call */ cssDivider('navbar-divider')
Loading history...
130 10
            ->cssItem('navbar-item')
131 10
            ->cssOptions('navbar-dropdown')
132 10
            ->encodeLabels($this->encodeLabels)
133 10
            ->items($items)
134 10
            ->options(ArrayHelper::getValue($parentItem, 'dropdownOptions', []))
135 10
            ->render() . "\n";
136
    }
137
138
    /**
139
     * Check to see if a child item is active optionally activating the parent.
140
     *
141
     * @param array $items
142
     * @param bool $active should the parent be active too
143
     *
144
     * @return array
145
     *
146
     * {@see items}
147
     */
148 10
    private function isChildActive(array $items, bool &$active): array
149
    {
150 10
        foreach ($items as $i => $child) {
151 9
            if ($this->isItemActive($child)) {
152 3
                ArrayHelper::setValue($items[$i], 'active', true);
153 3
                if ($this->activateParents) {
154 1
                    $active = $this->activateParents;
155
                }
156
            }
157
158 9
            if (is_array($child) && ($childItems = ArrayHelper::getValue($child, 'items')) && is_array($childItems)) {
159 1
                $activeParent = false;
160 1
                $items[$i]['items'] = $this->isChildActive($childItems, $activeParent);
161
162 1
                if ($activeParent) {
163 1
                    Html::addCssClass($items[$i]['options'], 'active');
164 1
                    $active = $activeParent;
165
                }
166
            }
167
        }
168
169 10
        return $items;
170
    }
171
172
    /**
173
     * Checks whether a menu item is active.
174
     *
175
     * This is done by checking if {@see currentPath} match that specified in the `url` option of the menu item. When
176
     * the `url` option of a menu item is specified in terms of an array, its first element is treated as the
177
     * currentPath for the item and the rest of the elements are the associated parameters. Only when its currentPath
178
     * and parameters match {@see currentPath}, respectively, will a menu item be considered active.
179
     *
180
     * @param array|string $item the menu item to be checked
181
     *
182
     * @return bool whether the menu item is active
183
     */
184 18
    private function isItemActive($item): bool
185
    {
186 18
        if (isset($item['active'])) {
187 5
            return ArrayHelper::getValue($item, 'active');
0 ignored issues
show
Bug introduced by
It seems like $item can also be of type string; however, parameter $array of Yiisoft\Arrays\ArrayHelper::getValue() does only seem to accept array|object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

187
            return ArrayHelper::getValue(/** @scrutinizer ignore-type */ $item, 'active');
Loading history...
Bug Best Practice introduced by
The expression return Yiisoft\Arrays\Ar...tValue($item, 'active') could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
188
        }
189
190 18
        if (isset($item['url']) && $this->currentPath !== '/' && $item['url'] === $this->currentPath && $this->activateItems) {
191 1
            return true;
192
        }
193
194 17
        return false;
195
    }
196
197
    /**
198
     * Renders widget items.
199
     *
200
     * @throws InvalidConfigException
201
     *
202
     * @return string
203
     */
204 19
    private function renderItems(): string
205
    {
206 19
        $items = [];
207
208 19
        foreach ($this->items as $item) {
209 19
            if (isset($item['visible']) && !$item['visible']) {
210 1
                continue;
211
            }
212
213 19
            $items[] = $this->renderItem($item);
214
        }
215
216 17
        return implode("\n", $items);
217
    }
218
219
    /**
220
     * Renders a widget's item.
221
     *
222
     * @param array $item the item to render.
223
     *
224
     * @return string the rendering result.
225
     *
226
     * @throws InvalidConfigException
227
     */
228 19
    private function renderItem(array $item): string
229
    {
230 19
        if (!isset($item['label'])) {
231 1
            throw new InvalidConfigException("The 'label' option is required.");
232
        }
233
234 18
        $this->encodeLabels = $item['encode'] ?? $this->encodeLabels;
235
236 18
        if ($this->encodeLabels) {
237 16
            $label = Html::encode($item['label']);
238
        } else {
239 2
            $label = $item['label'];
240
        }
241
242 18
        $options = ArrayHelper::getValue($item, 'options', []);
243 18
        $items = ArrayHelper::getValue($item, 'items');
244 18
        $url = ArrayHelper::getValue($item, 'url', '#');
245 18
        $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
246 18
        $disabled = ArrayHelper::getValue($item, 'disabled', false);
247
248 18
        $active = $this->isItemActive($item);
249
250 18
        if (isset($items)) {
251 10
            $this->dropdown = true;
252
253 10
            Html::addCssClass($options, 'navbar-item has-dropdown is-hoverable');
254
255 10
            if (is_array($items)) {
256 10
                $items = $this->isChildActive($items, $active);
257 10
                $items = $this->renderDropdown($items, $item);
258
            }
259
        }
260
261 17
        Html::addCssClass($linkOptions, 'navbar-item');
262
263 17
        if ($disabled) {
264 1
            Html::addCssStyle($linkOptions, 'opacity:.65; pointer-events:none;');
265
        }
266
267 17
        if ($this->activateItems && $active) {
268 2
            Html::addCssClass($linkOptions, 'is-active');
269
        }
270
271
272 17
        if ($this->dropdown) {
273
            return
274 9
                html::beginTag('div', $options) . "\n" .
275 9
                    Html::a($label, $url, ['class' => 'navbar-link']) . "\n" .
276 9
                    $items;
277
        }
278
279 13
        return Html::a($label, $url, $linkOptions);
280
    }
281
}
282