Passed
Pull Request — master (#51)
by Wilmer
02:18
created

Breadcrumbs::attributes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
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;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Yiisoft\Yii\Bulma\Widget. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

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