Passed
Pull Request — master (#22)
by Mr.
02:17
created

Dropdown::run()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 11
ccs 6
cts 6
cp 1
crap 2
rs 10
c 0
b 0
f 0
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\Html\Html;
11
12
use function array_key_exists;
13
use function array_merge;
14
use function array_merge_recursive;
15
use function is_string;
16
17
/**
18
 * Dropdown renders a Bootstrap dropdown menu component.
19
 *
20
 * For example,
21
 *
22
 * ```php
23
 * <div class="dropdown">
24
 *     <?php
25
 *         echo Dropdown::widget()
26
 *             ->items([
27
 *                 ['label' => 'DropdownA', 'url' => '/'],
28
 *                 ['label' => 'DropdownB', 'url' => '#'],
29
 *             ]);
30
 *     ?>
31
 * </div>
32
 * ```
33
 */
34
class Dropdown extends Widget
35
{
36
    private array $items = [];
37
    private bool $encodeLabels = true;
38
    private array $submenuOptions = [];
39
    private array $options = [];
40
41 15
    protected function run(): string
42
    {
43 15
        if (!isset($this->options['id'])) {
44 14
            $this->options['id'] = "{$this->getId()}-dropdown";
45
        }
46
47 15
        Html::addCssClass($this->options, ['widget' => 'dropdown-menu']);
48
49 15
        $this->registerClientEvents($this->options['id']);
50
51 15
        return $this->renderItems($this->items, $this->options);
52
    }
53
54
    /**
55
     * Renders menu items.
56
     *
57
     * @param array $items the menu items to be rendered
58
     * @param array $options the container HTML attributes
59
     *
60
     * @throws JsonException|RuntimeException if the label option is not specified in one of the items.
61
     *
62
     * @return string the rendering result.
63
     */
64 15
    protected function renderItems(array $items, array $options = []): string
65
    {
66 15
        $lines = [];
67
68 15
        foreach ($items as $item) {
69 15
            if (is_string($item)) {
70 3
                $item = ['label' => $item, 'encode' => false, 'enclose' => false];
71
            }
72
73 15
            if (isset($item['visible']) && !$item['visible']) {
74 3
                continue;
75
            }
76
77 15
            if (!array_key_exists('label', $item)) {
78
                throw new RuntimeException("The 'label' option is required.");
79
            }
80
81 15
            $encodeLabel = $item['encode'] ?? $this->encodeLabels;
82 15
            $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
83 15
            $itemOptions = ArrayHelper::getValue($item, 'options', []);
84 15
            $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
85 15
            $active = ArrayHelper::getValue($item, 'active', false);
86 15
            $disabled = ArrayHelper::getValue($item, 'disabled', false);
87 15
            $enclose = ArrayHelper::getValue($item, 'enclose', true);
88
89 15
            Html::addCssClass($linkOptions, 'dropdown-item');
90
91 15
            if ($disabled) {
92 1
                ArrayHelper::setValue($linkOptions, 'tabindex', '-1');
93 1
                ArrayHelper::setValue($linkOptions, 'aria-disabled', 'true');
94 1
                Html::addCssClass($linkOptions, 'disabled');
95 15
            } elseif ($active) {
96 2
                Html::addCssClass($linkOptions, 'active');
97
            }
98
99 15
            $url = $item['url'] ?? null;
100
101 15
            if (empty($item['items'])) {
102 15
                if ($label === '-') {
103 3
                    $content = Html::div('', ['class' => 'dropdown-divider']);
104 15
                } elseif ($enclose === false) {
105 1
                    $content = $label;
106 15
                } elseif ($url === null) {
107 7
                    $content = Html::tag('h6', $label, ['class' => 'dropdown-header']);
108
                } else {
109 9
                    $content = Html::a($label, $url, $linkOptions);
110
                }
111
112 15
                $lines[] = $content;
113
            } else {
114 3
                $submenuOptions = $this->submenuOptions;
115
116 3
                if (isset($item['submenuOptions'])) {
117 1
                    $submenuOptions = array_merge($submenuOptions, $item['submenuOptions']);
118
                }
119
120 3
                Html::addCssClass($submenuOptions, ['dropdown-menu']);
121 3
                Html::addCssClass($linkOptions, ['dropdown-toggle']);
122
123 3
                $lines[] = Html::a($label, $url, array_merge([
124 3
                    'data-bs-toggle' => 'dropdown',
125
                    'aria-haspopup' => 'true',
126
                    'aria-expanded' => 'false',
127
                    'role' => 'button',
128 3
                ], $linkOptions))
129
130 3
                . Html::tag(
131 3
                    'ul',
132 3
                    self::widget()
133 3
                        ->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

133
                        ->/** @scrutinizer ignore-call */ items($item['items'])
Loading history...
134 3
                        ->options($submenuOptions)
135 3
                        ->submenuOptions($submenuOptions)
136 3
                        ->encodeLabels($this->encodeLabels)
137 3
                        ->render(),
138 3
                    array_merge_recursive(['aria-expanded' => 'false', 'class' => ['dropdown'], 'encode' => false], $itemOptions
139
                    )
140
                );
141
            }
142
        }
143
144 15
        return Html::ul(
145 15
            $lines,
146 15
            array_merge_recursive(['aria-expanded' => 'false', 'encode' => false], $options)
147
        );
148
        return Html::div($menu, $options);
0 ignored issues
show
Unused Code introduced by
return Yiisoft\Html\Html::div($menu, $options) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
149
    }
150
151
    /**
152
     * List of menu items in the dropdown. Each array element can be either an HTML string, or an array representing a
153
     * single menu with the following structure:
154
     *
155
     * - label: string, required, the label of the item link.
156
     * - encode: bool, optional, whether to HTML-encode item label.
157
     * - url: string|array, optional, the URL of the item link. This will be processed by {@see currentPath}.
158
     *   If not set, the item will be treated as a menu header when the item has no sub-menu.
159
     * - visible: bool, optional, whether this menu item is visible. Defaults to true.
160
     * - linkOptions: array, optional, the HTML attributes of the item link.
161
     * - options: array, optional, the HTML attributes of the item.
162
     * - items: array, optional, the submenu items. The structure is the same as this property.
163
     *   Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it.
164
     * - submenuOptions: array, optional, the HTML attributes for sub-menu container tag. If specified it will be
165
     *   merged with {@see submenuOptions}.
166
     *
167
     * To insert divider use `-`.
168
     *
169
     * @param array $value
170
     *
171
     * @return $this
172
     */
173 15
    public function items(array $value): self
174
    {
175 15
        $this->items = $value;
176
177 15
        return $this;
178
    }
179
180
    /**
181
     * Whether the labels for header items should be HTML-encoded.
182
     *
183
     * @param bool $value
184
     *
185
     * @return $this
186
     */
187 11
    public function encodeLabels(bool $value): self
188
    {
189 11
        $this->encodeLabels = $value;
190
191 11
        return $this;
192
    }
193
194
    /**
195
     * The HTML attributes for sub-menu container tags.
196
     *
197
     * @param array $value
198
     *
199
     * @return $this
200
     */
201 3
    public function submenuOptions(array $value): self
202
    {
203 3
        $this->submenuOptions = $value;
204
205 3
        return $this;
206
    }
207
208
    /**
209
     * @param array $value the HTML attributes for the widget container tag. The following special options are
210
     * recognized.
211
     *
212
     * @return $this
213
     *
214
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
215
     */
216 11
    public function options(array $value): self
217
    {
218 11
        $this->options = $value;
219
220 11
        return $this;
221
    }
222
}
223