Passed
Pull Request — master (#35)
by Wilmer
02:16
created

Dropdown::renderItems()   D

Complexity

Conditions 16
Paths 197

Size

Total Lines 88
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 54
CRAP Score 16.0015

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 16
eloc 56
c 2
b 0
f 0
nc 197
nop 2
dl 0
loc 88
ccs 54
cts 55
cp 0.9818
crap 16.0015
rs 4.7583

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\Bootstrap5;
6
7
use JsonException;
8
use RuntimeException;
9
use Yiisoft\Arrays\ArrayHelper;
10
use Yiisoft\Factory\Exceptions\InvalidConfigException;
11
use Yiisoft\Html\Html;
12
13
use function array_key_exists;
14
use function array_merge;
15
use function array_merge_recursive;
16
use function is_string;
17
18
/**
19
 * Dropdown renders a Bootstrap dropdown menu component.
20
 *
21
 * For example,
22
 *
23
 * ```php
24
 * <div class="dropdown">
25
 *     <?php
26
 *         echo Dropdown::widget()
27
 *             ->withItems([
28
 *                 ['label' => 'DropdownA', 'url' => '/'],
29
 *                 ['label' => 'DropdownB', 'url' => '#'],
30
 *             ]);
31
 *     ?>
32
 * </div>
33
 * ```
34
 */
35
final class Dropdown extends Widget
36
{
37
    private array $items = [];
38
    private bool $encodeLabels = true;
39
    private bool $encodeTags = false;
40
    private array $submenuOptions = [];
41
    private array $options = [];
42
43 25
    protected function run(): string
44
    {
45 25
        if (!isset($this->options['id'])) {
46 24
            $this->options['id'] = "{$this->getId()}-dropdown";
47
        }
48
49
        /** @psalm-suppress InvalidArgument */
50 25
        Html::addCssClass($this->options, ['widget' => 'dropdown-menu']);
51
52 25
        if ($this->encodeTags === false) {
53 25
            $this->options = array_merge($this->options, ['itemOptions' => ['encode' => false], 'encode' => false]);
54
        }
55
56 25
        return $this->renderItems($this->items, $this->options);
57
    }
58
59
    /**
60
     * List of menu items in the dropdown. Each array element can be either an HTML string, or an array representing a
61
     * single menu with the following structure:
62
     *
63
     * - label: string, required, the label of the item link.
64
     * - encode: bool, optional, whether to HTML-encode item label.
65
     * - url: string|array, optional, the URL of the item link. This will be processed by {@see currentPath}.
66
     *   If not set, the item will be treated as a menu header when the item has no sub-menu.
67
     * - visible: bool, optional, whether this menu item is visible. Defaults to true.
68
     * - linkOptions: array, optional, the HTML attributes of the item link.
69
     * - options: array, optional, the HTML attributes of the item.
70
     * - items: array, optional, the submenu items. The structure is the same as this property.
71
     *   Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it.
72
     * - submenuOptions: array, optional, the HTML attributes for sub-menu container tag. If specified it will be
73
     *   merged with {@see submenuOptions}.
74
     *
75
     * To insert divider use `-`.
76
     *
77
     * @param array $value
78
     *
79
     * @return $this
80
     */
81 25
    public function withItems(array $value): self
82
    {
83 25
        $new = clone $this;
84 25
        $new->items = $value;
85
86 25
        return $new;
87
    }
88
89
    /**
90
     * When tags Labels HTML should not be encoded.
91
     *
92
     * @return $this
93
     */
94 1
    public function withoutEncodeLabels(): self
95
    {
96 1
        $new = clone $this;
97 1
        $new->encodeLabels = false;
98
99 1
        return $new;
100
    }
101
102
    /**
103
     * The HTML attributes for sub-menu container tags.
104
     *
105
     * @param array $value
106
     *
107
     * @return $this
108
     */
109 4
    public function withSubmenuOptions(array $value): self
110
    {
111 4
        $new = clone $this;
112 4
        $new->submenuOptions = $value;
113
114 4
        return $new;
115
    }
116
117
    /**
118
     * @param array $value the HTML attributes for the widget container tag. The following special options are
119
     * recognized.
120
     *
121
     * @return $this
122
     *
123
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
124
     */
125 13
    public function withOptions(array $value): self
126
    {
127 13
        $new = clone $this;
128 13
        $new->options = $value;
129
130 13
        return $new;
131
    }
132
133
    /**
134
     * Allows you to disable the encoding tags html.
135
     *
136
     * @return self
137
     */
138 1
    public function withEncodeTags(): self
139
    {
140 1
        $new = clone $this;
141 1
        $new->encodeTags = true;
142
143 1
        return $new;
144
    }
145
146
    /**
147
     * Renders menu items.
148
     *
149
     * @param array $items the menu items to be rendered
150
     * @param array $options the container HTML attributes
151
     *
152
     * @throws InvalidConfigException|JsonException|RuntimeException if the label option is not specified in one of the
153
     * items.
154
     *
155
     * @return string the rendering result.
156
     */
157 25
    private function renderItems(array $items, array $options = []): string
158
    {
159 25
        $lines = [];
160
161 25
        foreach ($items as $item) {
162 25
            if (is_string($item)) {
163 3
                $item = ['label' => $item, 'encode' => false, 'enclose' => false];
164
            }
165
166 25
            if (isset($item['visible']) && !$item['visible']) {
167 3
                continue;
168
            }
169
170 25
            if (!array_key_exists('label', $item)) {
171 1
                throw new RuntimeException("The 'label' option is required.");
172
            }
173
174 24
            $encodeLabel = $item['encode'] ?? $this->encodeLabels;
175 24
            $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
176 24
            $itemOptions = ArrayHelper::getValue($item, 'options', []);
177 24
            $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
178 24
            $active = ArrayHelper::getValue($item, 'active', false);
179 24
            $disabled = ArrayHelper::getValue($item, 'disabled', false);
180 24
            $enclose = ArrayHelper::getValue($item, 'enclose', true);
181
182 24
            Html::addCssClass($linkOptions, ['widget' => 'dropdown-item']);
183
184
            /** @psalm-suppress ConflictingReferenceConstraint */
185 24
            if ($this->encodeTags === false) {
186 24
                ArrayHelper::setValue($linkOptions, 'encode', false);
187 24
                ArrayHelper::setValue($itemOptions, 'encode', false);
188
            }
189
190 24
            if ($disabled) {
191 2
                ArrayHelper::setValue($linkOptions, 'tabindex', '-1');
192 2
                ArrayHelper::setValue($linkOptions, 'aria-disabled', 'true');
193 2
                Html::addCssClass($linkOptions, ['disable' => 'disabled']);
194 24
            } elseif ($active) {
195 4
                Html::addCssClass($linkOptions, ['active' => 'active']);
196
            }
197
198 24
            $url = $item['url'] ?? null;
199
200 24
            if (empty($item['items'])) {
201 24
                if ($label === '-') {
202 3
                    $content = Html::div('', ['class' => 'dropdown-divider']);
203 24
                } elseif ($enclose === false) {
204 1
                    $content = $label;
205 24
                } elseif ($url === null) {
206 10
                    $content = Html::tag('h6', $label, ['class' => 'dropdown-header']);
207
                } else {
208 18
                    $content = Html::a($label, $url, $linkOptions);
209
                }
210
211 24
                $lines[] = $content;
212
            } else {
213 4
                $submenuOptions = $this->submenuOptions;
214
215 4
                if (isset($item['submenuOptions'])) {
216 1
                    $submenuOptions = array_merge($submenuOptions, $item['submenuOptions']);
217
                }
218
219 4
                Html::addCssClass($submenuOptions, ['submenu' => 'dropdown-menu']);
220 4
                Html::addCssClass($linkOptions, ['toggle' => 'dropdown-toggle']);
221
222 4
                $itemOptions = array_merge_recursive(['class' => ['dropdown'], 'aria-expanded' => 'false'], $itemOptions);
223
224 4
                $dropdown = self::widget()
225 4
                    ->withItems($item['items'])
0 ignored issues
show
Bug introduced by
The method withItems() does not exist on Yiisoft\Widget\Widget. It seems like you code against a sub-type of Yiisoft\Widget\Widget such as Yiisoft\Yii\Bootstrap5\Nav or Yiisoft\Yii\Bootstrap5\Carousel or Yiisoft\Yii\Bootstrap5\Dropdown or Yiisoft\Yii\Bootstrap5\Tabs or Yiisoft\Yii\Bootstrap5\Accordion. ( Ignorable by Annotation )

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

225
                    ->/** @scrutinizer ignore-call */ withItems($item['items'])
Loading history...
226 4
                    ->withOptions($submenuOptions)
227 4
                    ->withSubmenuOptions($submenuOptions);
228
229 4
                if ($this->encodeLabels === false) {
230
                    $dropdown = $dropdown->withoutEncodeLabels();
231
                }
232
233 4
                ArrayHelper::setValue($linkOptions, 'data-bs-toggle', 'dropdown');
234 4
                ArrayHelper::setValue($linkOptions, 'aria-haspopup', 'true');
235 4
                ArrayHelper::setValue($linkOptions, 'aria-expanded', 'false');
236 4
                ArrayHelper::setValue($linkOptions, 'role', 'button');
237
238 4
                $lines[] = Html::a($label, $url, $linkOptions) . Html::tag('ul', $dropdown->render(), $itemOptions);
239
            }
240
        }
241
242 24
        $options = array_merge(['aria-expanded' => 'false'], $options);
243
244 24
        return Html::ul($lines, $options);
245
    }
246
}
247