Passed
Push — master ( 747b10...d3891b )
by Alexander
01:42
created

Breadcrumbs::items()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bulma;
6
7
use JsonException;
8
use Yiisoft\Arrays\ArrayHelper;
9
use Yiisoft\Html\Html;
10
11
use function array_key_exists;
12
use function array_merge;
13
use function is_array;
14
use function strtr;
15
16
/**
17
 * The Bulma breadcrumb is a simple navigation component.
18
 *
19
 * ```php
20
 * echo Breadcrumbs::widget()->items([
21
 *     ['label' => 'Info'],
22
 *     ['label' => 'Contacts'],
23
 * ]);
24
 * ```
25
 *
26
 * @link https://bulma.io/documentation/components/breadcrumb/
27
 */
28
class Breadcrumbs extends Widget
29
{
30
    private bool $encodeLabels = true;
31
    private array $homeItem = [];
32
    private bool $withoutHomeItem = false;
33
    private string $itemTemplate = "<li>{icon}{link}</li>\n";
34
    private string $activeItemTemplate = "<li class=\"is-active\"><a aria-current=\"page\">{icon}{label}</li>\n";
35
    private array $items = [];
36
    private array $options = [];
37 12
    private array $itemsOptions = [];
38
39 12
    protected function run(): string
40 1
    {
41
        if (empty($this->items)) {
42
            return '';
43 11
        }
44
45
        $this->buildOptions();
46 11
47 11
        return
48 11
            Html::beginTag('nav', $this->options) . "\n" .
49 10
                Html::beginTag('ul', $this->itemsOptions) . "\n" .
50 10
                    implode('', $this->renderItems()) .
51
                Html::endTag('ul') . "\n" .
52
            Html::endTag('nav');
53
    }
54
55
    /**
56
     * Whether to HTML-encode the link labels.
57
     *
58
     * @param bool $value
59
     *
60 1
     * @return self
61
     */
62 1
    public function encodeLabels(bool $value): self
63 1
    {
64
        $new = clone $this;
65
        $new->encodeLabels = $value;
66
        return $new;
67
    }
68
69
    /**
70
     * Do not render home item.
71
     * @return self
72
     */
73
    public function withoutHomeItem(): self
74
    {
75
        $new = clone $this;
76
        $new->withoutHomeItem = true;
77
        return $new;
78 6
    }
79
80 6
    /**
81 6
     * The first item in the breadcrumbs (called home link).
82
     *
83
     * Please refer to {@see $items} on the format.
84
     *
85
     * @param array $value
86
     *
87
     * @return self
88
     */
89
    public function homeItem(array $value): self
90
    {
91
        $new = clone $this;
92 1
        $new->homeItem = $value;
93
        return $new;
94 1
    }
95 1
96
    /**
97
     * The template used to render each inactive item in the breadcrumbs. The token `{link}` will be replaced with the
98
     * actual HTML link for each inactive item.
99
     *
100
     * @param string $value
101
     *
102
     * @return self
103
     */
104
    public function itemTemplate(string $value): self
105
    {
106 1
        $new = clone $this;
107
        $new->itemTemplate = $value;
108 1
        return $new;
109 1
    }
110
111
    /**
112
     * The template used to render each active item in the breadcrumbs. The token `{link}` will be replaced with the
113
     * actual HTML link for each active item.
114
     *
115
     * @param string $value
116
     *
117
     * @return self
118
     */
119
    public function activeItemTemplate(string $value): self
120
    {
121
        $new = clone $this;
122
        $new->activeItemTemplate = $value;
123
        return $new;
124
    }
125
126
    /**
127
     * List of items to appear in the breadcrumb. If this property is empty, the widget will not render anything. Each
128 12
     * array element represents a single link in the breadcrumb with the following structure:
129
     *
130 12
     * ```php
131 12
     * [
132
     *     'label' => 'label of the link',  // required
133
     *     'url' => 'url of the link',      // optional, will be processed by Url::to()
134
     *     'template' => 'own template of the item', // optional, if not set $this->itemTemplate will be used
135
     * ]
136
     * ```
137
     *
138
     * @param array $value
139
     *
140
     * @return self
141
     */
142
    public function items(array $value): self
143 2
    {
144
        $new = clone $this;
145 2
        $new->items = $value;
146 2
        return $new;
147
    }
148
149
    /**
150
     * The HTML attributes for the widget container nav tag.
151
     *
152
     * @param array $value
153
     *
154
     * @return self
155
     *
156
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
157
     */
158 1
    public function options(array $value): self
159
    {
160 1
        $new = clone $this;
161 1
        $new->options = $value;
162
        return $new;
163
    }
164 11
165
    /**
166 11
     * The HTML attributes for the items widget.
167 11
     *
168
     * @param array $value
169
     *
170 11
     * @return self
171 11
     *
172 11
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
173 11
     */
174
    public function itemsOptions(array $value): self
175 11
    {
176
        $new = clone $this;
177 11
        $new->itemsOptions = $value;
178
        return $new;
179 11
    }
180
181 11
    private function buildOptions(): void
182
    {
183 11
        if (!isset($this->options['id'])) {
184 1
            $this->options['id'] = "{$this->getId()}-breadcrumbs";
185 1
        }
186 1
187
        $this->options = $this->addOptions(
188
            array_merge(
189 11
                $this->options,
190
                ['aria-label' => 'breadcrumbs']
191
            ),
192
            'breadcrumb'
193
        );
194
    }
195
196
    private function renderIcon(string $icon, array $iconOptions): string
197
    {
198
        $html = '';
199
200
        if ($icon !== '') {
201
            $html = Html::beginTag('span', $iconOptions) .
202
                Html::tag('i', '', ['class' => $icon]) .
203 11
                Html::endTag('span');
204
        }
205 11
206
        return $html;
207 11
    }
208 11
209
    /**
210 11
     * Renders a single breadcrumb item.
211 1
     *
212
     * @param array $link the link to be rendered. It must contain the "label" element. The "url" element is optional.
213
     * @param string $template the template to be used to rendered the link. The token "{link}" will be replaced by the
214 11
     * link.
215 1
     *
216
     * @throws \InvalidArgumentException|JsonException if `$link` does not have "label" element.
217
     *
218 11
     * @return string the rendering result
219
     */
220 11
    private function renderItem(array $link, string $template): string
221 11
    {
222
        $encodeLabel = ArrayHelper::remove($link, 'encode', $this->encodeLabels);
223 1
224
        $icon = '';
225
        $iconOptions = [];
226 11
227 1
        if (isset($link['icon'])) {
228
            $icon = $link['icon'];
229
        }
230 11
231 11
        if (isset($link['iconOptions']) && is_array($link['iconOptions'])) {
232 11
            $iconOptions = $this->addOptions($iconOptions, 'icon');
233 11
        }
234
235 2
        unset($link['icon'], $link['iconOptions']);
236
237
        if (array_key_exists('label', $link)) {
238 11
            $label = $encodeLabel ? Html::encode($link['label']) : $link['label'];
239 11
        } else {
240 11
            throw new \InvalidArgumentException('The "label" element is required for each link.');
241
        }
242
243
        if (isset($link['template'])) {
244 11
            $template = $link['template'];
245
        }
246 11
247
        if (isset($link['url'])) {
248 11
            $options = $link;
249
            unset($options['template'], $options['label'], $options['url'], $options['icon']);
250 11
            $linkHtml = Html::a($label, $link['url'], $options);
251 11
        } else {
252 1
            $linkHtml = $label;
253
        }
254
255 11
        return strtr(
256
            $template,
257
            ['{label}' => $label, '{link}' => $linkHtml, '{icon}' => $this->renderIcon($icon, $iconOptions)]
258 10
        );
259
    }
260
261 11
    private function renderItems(): array
262
    {
263 11
        $links = [];
264 5
265
        if ($this->withoutHomeItem === false) {
266
            $links[] = $this->renderHomeLink();
267 11
        }
268
269
        foreach ($this->items as $link) {
270
            if (!is_array($link)) {
271
                $link = ['label' => $link];
272
            }
273
274
            $links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate);
275
        }
276
277
        return $links;
278
    }
279
280
    private function renderHomeLink(): string
281
    {
282
        if ($this->homeItem === []) {
283
            $this->homeItem = ['label' => 'Home', 'url' => '/'];
284
        }
285
286
        return $this->renderItem($this->homeItem, $this->itemTemplate);
287
    }
288
}
289