Test Failed
Pull Request — master (#41)
by Sergei
06:46
created

Dropdown::items()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 6
ccs 1
cts 1
cp 1
crap 1
rs 10
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
 *             ->items([
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
        return $this->renderItems($this->items, $this->options);
53 25
    }
54
55
    /**
56 25
     * List of menu items in the dropdown. Each array element can be either an HTML string, or an array representing a
57
     * single menu with the following structure:
58
     *
59
     * - label: string, required, the label of the item link.
60
     * - encode: bool, optional, whether to HTML-encode item label.
61
     * - url: string|array, optional, the URL of the item link. This will be processed by {@see currentPath}.
62
     *   If not set, the item will be treated as a menu header when the item has no sub-menu.
63
     * - visible: bool, optional, whether this menu item is visible. Defaults to true.
64
     * - linkOptions: array, optional, the HTML attributes of the item link.
65
     * - options: array, optional, the HTML attributes of the item.
66
     * - items: array, optional, the submenu items. The structure is the same as this property.
67
     *   Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it.
68
     * - submenuOptions: array, optional, the HTML attributes for sub-menu container tag. If specified it will be
69
     *   merged with {@see submenuOptions}.
70
     *
71
     * To insert divider use `-`.
72
     *
73
     * @param array $value
74
     *
75
     * @return $this
76
     */
77
    public function items(array $value): self
78
    {
79
        $new = clone $this;
80
        $new->items = $value;
81 25
82
        return $new;
83 25
    }
84 25
85
    /**
86 25
     * When tags Labels HTML should not be encoded.
87
     *
88
     * @return $this
89
     */
90
    public function withoutEncodeLabels(): self
91
    {
92
        $new = clone $this;
93
        $new->encodeLabels = false;
94 3
95
        return $new;
96 3
    }
97 3
98
    /**
99 3
     * The HTML attributes for sub-menu container tags.
100
     *
101
     * @param array $value
102
     *
103
     * @return $this
104
     */
105
    public function submenuOptions(array $value): self
106
    {
107
        $new = clone $this;
108
        $new->submenuOptions = $value;
109 4
110
        return $new;
111 4
    }
112 4
113
    /**
114 4
     * @param array $value the HTML attributes for the widget container tag. The following special options are
115
     * recognized.
116
     *
117
     * @return $this
118
     *
119
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
120
     */
121
    public function options(array $value): self
122
    {
123
        $new = clone $this;
124
        $new->options = $value;
125 14
126
        return $new;
127 14
    }
128 14
129
    /**
130 14
     * Renders menu items.
131
     *
132
     * @param array $items the menu items to be rendered
133
     * @param array $options the container HTML attributes
134
     *
135
     * @throws InvalidConfigException|JsonException|RuntimeException if the label option is not specified in one of the
136
     * items.
137
     *
138
     * @return string the rendering result.
139
     */
140
    private function renderItems(array $items, array $options = []): string
141
    {
142
        $lines = [];
143
144 25
        foreach ($items as $item) {
145
            if (is_string($item)) {
146 25
                $item = ['label' => $item, 'encode' => false, 'enclose' => false];
147
            }
148 25
149 25
            if (isset($item['visible']) && !$item['visible']) {
150 3
                continue;
151
            }
152
153 25
            if (!array_key_exists('label', $item)) {
154 3
                throw new RuntimeException("The 'label' option is required.");
155
            }
156
157 25
            $encodeLabel = $item['encode'] ?? $this->encodeLabels;
158 1
            $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
159
            $itemOptions = ArrayHelper::getValue($item, 'options', []);
160
            $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
161 24
            $active = ArrayHelper::getValue($item, 'active', false);
162 24
            $disabled = ArrayHelper::getValue($item, 'disabled', false);
163 24
            $enclose = ArrayHelper::getValue($item, 'enclose', true);
164 24
165 24
            Html::addCssClass($linkOptions, ['widget' => 'dropdown-item']);
166 24
167 24
            if ($disabled) {
168
                ArrayHelper::setValue($linkOptions, 'tabindex', '-1');
169 24
                ArrayHelper::setValue($linkOptions, 'aria-disabled', 'true');
170 24
                Html::addCssClass($linkOptions, ['disable' => 'disabled']);
171 24
            } elseif ($active) {
172
                Html::addCssClass($linkOptions, ['active' => 'active']);
173
            }
174 24
175
            $url = $item['url'] ?? null;
176 24
177 2
            /** @psalm-suppress ConflictingReferenceConstraint */
178 2
            if (empty($item['items'])) {
179 2
                if ($label === '-') {
180 24
                    $content = Html::div('', ['class' => 'dropdown-divider']);
181 4
                } elseif ($enclose === false) {
182
                    $content = $label;
183
                } elseif ($url === null) {
184 24
                    $content = Html::tag('h6', $label, ['class' => 'dropdown-header']);
185
                } else {
186
                    $content = Html::a($label, $url, $linkOptions)->encode($this->encodeTags);
187 24
                }
188 24
189 3
                $lines[] = $content;
190 24
            } else {
191 1
                $submenuOptions = $this->submenuOptions;
192 24
193 11
                if (isset($item['submenuOptions'])) {
194
                    $submenuOptions = array_merge($submenuOptions, $item['submenuOptions']);
195 17
                }
196
197
                Html::addCssClass($submenuOptions, ['submenu' => 'dropdown-menu']);
198 24
                Html::addCssClass($linkOptions, ['toggle' => 'dropdown-toggle']);
199
200 4
                $itemOptions = array_merge_recursive(['class' => ['dropdown'], 'aria-expanded' => 'false'], $itemOptions);
201
202 4
                $dropdown = self::widget()
203 1
                    ->items($item['items'])
0 ignored issues
show
Bug introduced by
The method items() 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

203
                    ->/** @scrutinizer ignore-call */ items($item['items'])
Loading history...
204
                    ->options($submenuOptions)
205
                    ->submenuOptions($submenuOptions);
206 4
207 4
                if ($this->encodeLabels === false) {
208
                    $dropdown = $dropdown->withoutEncodeLabels();
209 4
                }
210
211 4
                ArrayHelper::setValue($linkOptions, 'data-bs-toggle', 'dropdown');
212 4
                ArrayHelper::setValue($linkOptions, 'aria-haspopup', 'true');
213 4
                ArrayHelper::setValue($linkOptions, 'aria-expanded', 'false');
214 4
                ArrayHelper::setValue($linkOptions, 'role', 'button');
215
216 4
                $lines[] = Html::a($label, $url, $linkOptions)->encode($this->encodeTags) .
217 1
                    Html::tag('ul', $dropdown->render(), $itemOptions)->encode($this->encodeTags);
218
            }
219
        }
220 4
221 4
        $options = array_merge(['aria-expanded' => 'false'], $options);
222 4
223 4
        return Html::ul()
0 ignored issues
show
Bug introduced by
The call to Yiisoft\Html\Html::ul() has too few arguments starting with items. ( Ignorable by Annotation )

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

223
        return Html::/** @scrutinizer ignore-call */ ul()

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
224
            ->strings($lines, $this->encodeTags)
225 4
            ->attributes($options)
226
            ->render();
227
    }
228
}
229