Breadcrumbs::renderItem()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 47
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 4

Importance

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