Passed
Pull Request — master (#61)
by Wilmer
03:03 queued 47s
created

Breadcrumbs   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 290
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 92
dl 0
loc 290
ccs 96
cts 96
cp 1
rs 10
c 1
b 0
f 0
wmc 26

14 Methods

Rating   Name   Duplication   Size   Complexity  
A attributes() 0 5 1
A renderIcon() 0 13 2
A encode() 0 5 1
A renderItems() 0 21 5
A id() 0 5 1
A items() 0 5 1
A itemsAttributes() 0 5 1
A ariaLabel() 0 5 1
A itemTemplate() 0 5 1
A homeItem() 0 11 2
A autoIdPrefix() 0 5 1
A run() 0 24 4
A activeItemTemplate() 0 5 1
A renderItem() 0 34 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bulma;
6
7
use InvalidArgumentException;
8
use Yiisoft\Html\Html;
9
use Yiisoft\Html\Tag\A;
10
use Yiisoft\Html\Tag\CustomTag;
11
use Yiisoft\Html\Tag\I;
12
use Yiisoft\Html\Tag\Span;
13
use Yiisoft\Widget\Widget;
14
15
use function array_key_exists;
16
use function implode;
17
use function is_array;
18
use function strtr;
19
20
/**
21
 * The Bulma breadcrumb is a simple navigation component.
22
 *
23
 * ```php
24
 * echo Breadcrumbs::widget()->items([
25
 *     ['label' => 'Info'],
26
 *     ['label' => 'Contacts'],
27
 * ]);
28
 * ```
29
 *
30
 * @link https://bulma.io/documentation/components/breadcrumb/
31
 */
32
final class Breadcrumbs extends Widget
33
{
34
    private string $activeItemTemplate = "<li class=\"is-active\"><a aria-current=\"page\">{link}</a></li>\n";
35
    private array $attributes = [];
36
    private string $autoIdPrefix = 'w';
37
    private bool $encode = false;
38
    private ?array $homeItem = ['label' => 'Home', 'url' => '/'];
39
    private array $items = [];
40
    private array $itemsAttributes = [];
41
    private string $itemTemplate = "<li>{link}</li>\n";
42
43
    /**
44
     * Returns a new instance with the specified active item template.
45
     *
46
     * @param string $value The template used to render each active item in the breadcrumbs. The token `{link}` will be
47
     * replaced with the actual HTML link for each active item.
48
     *
49
     * @return self
50
     */
51 2
    public function activeItemTemplate(string $value): self
52
    {
53 2
        $new = clone $this;
54 2
        $new->activeItemTemplate = $value;
55 2
        return $new;
56
    }
57
58
    /**
59
     * Defines a string value that labels the current element.
60
     *
61
     * @param string $value The value of the aria-label attribute.
62
     *
63
     * @return self
64
     *
65
     * @link https://www.w3.org/TR/wai-aria/#aria-label
66
     */
67 2
    public function ariaLabel(string $value): self
68
    {
69 2
        $new = clone $this;
70 2
        $new->attributes['aria-label'] = $value;
71 2
        return $new;
72
    }
73
74
    /**
75
     * The HTML attributes. The following special options are recognized.
76
     *
77
     * @param array $values Attribute values indexed by attribute names.
78
     *
79
     * @return self
80
     *
81
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} For details on how attributes are being rendered.
82
     */
83 3
    public function attributes(array $values): self
84
    {
85 3
        $new = clone $this;
86 3
        $new->attributes = $values;
87 3
        return $new;
88
    }
89
90
    /**
91
     * Returns a new instance with the specified prefix to the automatically generated widget IDs.
92
     *
93
     * @param string $value The prefix to the automatically generated widget IDs.
94
     *
95
     * @return self
96
     */
97 1
    public function autoIdPrefix(string $value): self
98
    {
99 1
        $new = clone $this;
100 1
        $new->autoIdPrefix = $value;
101 1
        return $new;
102
    }
103
104
    /**
105
     * Set encode to true to encode the output.
106
     *
107
     * @param bool $value Whether to encode the output.
108
     *
109
     * @return self
110
     */
111 2
    public function encode(bool $value): self
112
    {
113 2
        $new = clone $this;
114 2
        $new->encode = $value;
115 2
        return $new;
116
    }
117
118
    /**
119
     * Returns a new instance with the specified first item in the breadcrumbs (called home link).
120
     *
121
     * If a `false` is specified, the home item will not be rendered.
122
     *
123
     * @param array|null $value Please refer to {@see items()} on the format.
124
     *
125
     * @throws InvalidArgumentException If an empty array is specified.
126
     *
127
     * @return self
128
     */
129 10
    public function homeItem(?array $value): self
130
    {
131 10
        if ($value === []) {
132 1
            throw new InvalidArgumentException(
133
                'The home item cannot be an empty array. To disable rendering of the home item, specify null.',
134
            );
135
        }
136
137 9
        $new = clone $this;
138 9
        $new->homeItem = $value;
139 9
        return $new;
140
    }
141
142
    /**
143
     * Returns a new instance with the specified ID of the widget.
144
     *
145
     * @param string $value The ID of the widget.
146
     *
147
     * @return self
148
     */
149 1
    public function id(string $value): self
150
    {
151 1
        $new = clone $this;
152 1
        $new->attributes['id'] = $value;
153 1
        return $new;
154
    }
155
156
    /**
157
     * Returns a new instance with the specified list of items.
158
     *
159
     * @param array $value List of items to appear in the breadcrumbs. If this property is empty, the widget will not
160
     * render anything. Each array element represents a single item in the breadcrumbs with the following structure:
161
     *
162
     * ```php
163
     * [
164
     *     'label' => 'label of the link',  // required
165
     *     'url' => 'url of the link',      // optional, will be processed by Url::to()
166
     *     'template' => 'own template of the item', // optional, if not set $this->itemTemplate will be used
167
     * ]
168
     * ```
169
     *
170
     * @return self
171
     */
172 15
    public function items(array $value): self
173
    {
174 15
        $new = clone $this;
175 15
        $new->items = $value;
176 15
        return $new;
177
    }
178
179
    /**
180
     * Returns a new instance with the specified item HTML attributes.
181
     *
182
     * @param array $value The HTML attributes for the item's widget.
183
     *
184
     * @return self
185
     *
186
     * {@see Html::renderTagAttributes()} For details on how attributes are being rendered.
187
     */
188 2
    public function itemsAttributes(array $value): self
189
    {
190 2
        $new = clone $this;
191 2
        $new->itemsAttributes = $value;
192 2
        return $new;
193
    }
194
195
    /**
196
     * Returns a new instance with the specified item template.
197
     *
198
     * @param string $value The template used to render each inactive item in the breadcrumbs. The token `{link}` will
199
     * be replaced with the actual HTML link for each inactive item.
200
     *
201
     * @return self
202
     */
203 2
    public function itemTemplate(string $value): self
204
    {
205 2
        $new = clone $this;
206 2
        $new->itemTemplate = $value;
207 2
        return $new;
208
    }
209
210 14
    protected function run(): string
211
    {
212 14
        if (empty($this->items)) {
213 1
            return '';
214
        }
215
216 13
        $attributes = $this->attributes;
217 13
        $customTag = CustomTag::name('nav');
218
219 13
        Html::addCssClass($attributes, 'breadcrumb');
220
221 13
        if (!array_key_exists('aria-label', $attributes)) {
222 12
            $customTag = $customTag->attribute('aria-label', 'breadcrumbs');
223
        }
224
225 13
        if (!array_key_exists('id', $attributes)) {
226 13
            $customTag = $customTag->id(Html::generateId($this->autoIdPrefix) . '-breadcrumbs');
227
        }
228
229 13
        $content = "\n" . Html::openTag('ul', $this->itemsAttributes) . "\n" .
230 13
            implode('', $this->renderItems()) .
231 12
            Html::closeTag('ul') . "\n";
232
233 12
        return $customTag->content($content)->attributes($attributes)->encode(false)->render();
234
    }
235
236 13
    private function renderIcon(?string $icon, array $iconAttributes): string
237
    {
238 13
        $html = '';
239
240 13
        if ($icon !== null) {
241 1
            $html = Span::tag()
242 1
                ->attributes($iconAttributes)
243 1
                ->content(I::tag()->attributes(['class' => $icon, 'aria-hidden' => 'true'])->render())
244 1
                ->encode($this->encode)
245 1
                ->render();
246
        }
247
248 13
        return $html;
249
    }
250
251
    /**
252
     * Renders a single breadcrumb item.
253
     *
254
     * @param array $item The item to be rendered. It must contain the "label" element. The "url" element is optional.
255
     * @param string $template The template to be used to render the link. The token "{link}" will be replaced by the
256
     * link.
257
     *
258
     * @throws InvalidArgumentException If `$item` does not have "label" element.
259
     *
260
     * @return string The rendering result.
261
     */
262 13
    private function renderItem(array $item, string $template): string
263
    {
264 13
        if (!array_key_exists('label', $item)) {
265 1
            throw new InvalidArgumentException('The "label" element is required for each link.');
266
        }
267
268
        /** @var bool */
269 13
        $encode = $item['encode'] ?? $this->encode;
270 13
        unset($item['encode']);
271
        /** @var string|null */
272 13
        $icon = $item['icon'] ?? null;
273 13
        unset($item['icon']);
274
        /** @var array */
275 13
        $iconAttributes = $item['iconAttributes'] ?? [];
276 13
        unset($item['iconAttributes']);
277
        /** @var string */
278 13
        $template = $item['template'] ?? $template;
279 13
        unset($item['template']);
280
        /** @var string|null */
281 13
        $url = $item['url'] ?? null;
282 13
        unset($item['url']);
283 13
        $icon = $this->renderIcon($icon, $iconAttributes);
284
        /** @var string */
285 13
        $label = $item['label'];
286 13
        unset($item['label']);
287
288 13
        if ($icon !== '') {
289 1
            $label = $icon . Span::tag()->content($label)->render();
290
        }
291
292 13
        $link = $url !== null
293 13
            ? A::tag()->attributes($item)->content($label)->url($url)->encode($encode)->render() : $label;
294
295 13
        return strtr($template, ['{link}' => $link, '{label}' => $label, '{icon}' => $icon]);
296
    }
297
298
    /**
299
     * @psalm-return string[]
300
     */
301 13
    private function renderItems(): array
302
    {
303 13
        $renderItems = [];
304
305 13
        if ($this->homeItem !== null) {
306 12
            $renderItems[] = $this->renderItem($this->homeItem, $this->itemTemplate);
307
        }
308
309
        /** @psalm-var string[]|string $item */
310 13
        foreach ($this->items as $item) {
311 13
            if (!is_array($item)) {
312 1
                $item = ['label' => $item];
313
            }
314
315 13
            $renderItems[] = $this->renderItem(
316
                $item,
317 13
                isset($item['url']) ? $this->itemTemplate : $this->activeItemTemplate,
318
            );
319
        }
320
321 12
        return $renderItems;
322
    }
323
}
324