Passed
Push — master ( fdfa94...107742 )
by Alexander
02:22
created

Nav::renderItem()   B

Complexity

Conditions 11
Paths 97

Size

Total Lines 63
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 11

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 35
c 1
b 0
f 0
nc 97
nop 1
dl 0
loc 63
ccs 34
cts 34
cp 1
crap 11
rs 7.3166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

134
            ->/** @scrutinizer ignore-call */ dividerClass('navbar-divider')
Loading history...
135 11
            ->itemClass('navbar-item')
136 11
            ->itemsClass('navbar-dropdown')
137 11
            ->encloseByContainer(false)
138 11
            ->encodeLabels($this->encodeLabels)
139 11
            ->items($items)
140 11
            ->itemsOptions(ArrayHelper::getValue($parentItem, 'dropdownOptions', []))
141 11
            ->render() . "\n";
142
    }
143
144
    /**
145
     * Check to see if a child item is active optionally activating the parent.
146
     *
147
     * @param array $items
148
     * @param bool $active should the parent be active too
149
     *
150
     * @return array
151
     *
152
     * {@see items}
153
     */
154 11
    private function isChildActive(array $items, bool &$active): array
155
    {
156 11
        foreach ($items as $i => $child) {
157 10
            if ($this->isItemActive($child)) {
158 3
                ArrayHelper::setValue($items[$i], 'active', true);
159 3
                if ($this->activateParents) {
160 1
                    $active = $this->activateParents;
161
                }
162
            }
163
164 10
            if (is_array($child) && ($childItems = ArrayHelper::getValue($child, 'items')) && is_array($childItems)) {
165 1
                $activeParent = false;
166 1
                $items[$i]['items'] = $this->isChildActive($childItems, $activeParent);
167
168 1
                if ($activeParent) {
169 1
                    Html::addCssClass($items[$i]['options'], 'active');
170 1
                    $active = $activeParent;
171
                }
172
            }
173
        }
174
175 11
        return $items;
176
    }
177
178
    /**
179
     * Checks whether a menu item is active.
180
     *
181
     * This is done by checking if {@see currentPath} match that specified in the `url` option of the menu item. When
182
     * the `url` option of a menu item is specified in terms of an array, its first element is treated as the
183
     * currentPath for the item and the rest of the elements are the associated parameters. Only when its currentPath
184
     * and parameters match {@see currentPath}, respectively, will a menu item be considered active.
185
     *
186
     * @param array|string|object $item the menu item to be checked
187
     *
188
     * @return bool whether the menu item is active
189
     */
190 19
    private function isItemActive($item): bool
191
    {
192 19
        if (isset($item['active'])) {
193 5
            return ArrayHelper::getValue($item, 'active');
0 ignored issues
show
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...
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

193
            return ArrayHelper::getValue(/** @scrutinizer ignore-type */ $item, 'active');
Loading history...
194
        }
195
196 19
        if (isset($item['url']) && $this->currentPath !== '/' && $item['url'] === $this->currentPath && $this->activateItems) {
197 1
            return true;
198
        }
199
200 18
        return false;
201
    }
202
203 19
    private function renderIcon(string $label, string $icon, array $iconOptions): string
204
    {
205 19
        if ($icon !== '') {
206 1
            $label = Html::beginTag('span', $iconOptions) .
207 1
                Html::tag('i', '', ['class' => $icon]) .
208 1
                Html::endTag('span') .
209 1
                Html::tag('span', $label);
210
        }
211
212 19
        return $label;
213
    }
214
215
    /**
216
     * Renders widget items.
217
     *
218
     * @throws InvalidConfigException|JsonException
219
     *
220
     * @return string
221
     */
222 20
    private function renderItems(): string
223
    {
224 20
        $items = [];
225
226 20
        foreach ($this->items as $item) {
227 20
            if (isset($item['visible']) && !$item['visible']) {
228 1
                continue;
229
            }
230
231 20
            $items[] = $this->renderItem($item);
232
        }
233
234 18
        return implode("\n", $items);
235
    }
236
237
    /**
238
     * Renders a widget's item.
239
     *
240
     * @param array $item the item to render.
241
     *
242
     * @throws InvalidConfigException|JsonException
243
     *
244
     * @return string the rendering result.
245
     */
246 20
    private function renderItem(array $item): string
247
    {
248 20
        if (!isset($item['label'])) {
249 1
            throw new InvalidConfigException("The 'label' option is required.");
250
        }
251
252 19
        $this->encodeLabels = $item['encode'] ?? $this->encodeLabels;
253
254 19
        if ($this->encodeLabels) {
255 17
            $label = Html::encode($item['label']);
256
        } else {
257 3
            $label = $item['label'];
258
        }
259
260 19
        $iconOptions = [];
261
262 19
        $icon = $item['icon'] ?? '';
263
264 19
        if (array_key_exists('iconOptions', $item) && is_array($item['iconOptions'])) {
265 1
            $iconOptions = $this->addOptions($iconOptions, 'icon');
266
        }
267
268 19
        $label = $this->renderIcon($label, $icon, $iconOptions);
269
270 19
        $options = ArrayHelper::getValue($item, 'options', []);
271 19
        $items = ArrayHelper::getValue($item, 'items');
272 19
        $url = ArrayHelper::getValue($item, 'url', '#');
273 19
        $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
274 19
        $disabled = ArrayHelper::getValue($item, 'disabled', false);
275
276 19
        $active = $this->isItemActive($item);
277
278 19
        if (isset($items)) {
279 11
            $this->dropdown = true;
280
281 11
            Html::addCssClass($options, 'navbar-item has-dropdown is-hoverable');
282
283 11
            if (is_array($items)) {
284 11
                $items = $this->isChildActive($items, $active);
285 11
                $items = $this->renderDropdown($items, $item);
286
            }
287
        }
288
289 18
        Html::addCssClass($linkOptions, 'navbar-item');
290
291 18
        if ($disabled) {
292 1
            Html::addCssStyle($linkOptions, 'opacity:.65; pointer-events:none;');
293
        }
294
295
        /** @psalm-suppress ConflictingReferenceConstraint */
296 18
        if ($this->activateItems && $active) {
297 2
            Html::addCssClass($linkOptions, 'is-active');
298
        }
299
300
301 18
        if ($this->dropdown) {
302
            return
303 10
                html::beginTag('div', $options) . "\n" .
304 10
                    Html::a($label, $url, ['class' => 'navbar-link']) . "\n" .
305 10
                    $items;
306
        }
307
308 14
        return Html::a($label, $url, $linkOptions);
309
    }
310
}
311