Passed
Push — master ( d3891b...901043 )
by Alexander
02:19
created

Breadcrumbs::renderItems()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 8
c 0
b 0
f 0
nc 6
nop 0
dl 0
loc 17
ccs 9
cts 9
cp 1
crap 5
rs 9.6111
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
    private array $itemsOptions = [];
38
39 12
    protected function run(): string
40
    {
41 12
        if (empty($this->items)) {
42 1
            return '';
43
        }
44
45 11
        $this->buildOptions();
46
47
        return
48 11
            Html::beginTag('nav', $this->options) . "\n" .
49 11
                Html::beginTag('ul', $this->itemsOptions) . "\n" .
50 11
                    implode('', $this->renderItems()) .
51 10
                Html::endTag('ul') . "\n" .
52 10
            Html::endTag('nav');
53
    }
54
55
    /**
56
     * Whether to HTML-encode the link labels.
57
     *
58
     * @param bool $value
59
     *
60
     * @return self
61
     */
62 1
    public function encodeLabels(bool $value): self
63
    {
64 1
        $new = clone $this;
65 1
        $new->encodeLabels = $value;
66 1
        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
    }
79
80
    /**
81
     * 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 6
    public function homeItem(array $value): self
90
    {
91 6
        $new = clone $this;
92 6
        $new->homeItem = $value;
93 6
        return $new;
94
    }
95
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 1
    public function itemTemplate(string $value): self
105
    {
106 1
        $new = clone $this;
107 1
        $new->itemTemplate = $value;
108 1
        return $new;
109
    }
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 1
    public function activeItemTemplate(string $value): self
120
    {
121 1
        $new = clone $this;
122 1
        $new->activeItemTemplate = $value;
123 1
        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
     * array element represents a single link in the breadcrumb with the following structure:
129
     *
130
     * ```php
131
     * [
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 12
    public function items(array $value): self
143
    {
144 12
        $new = clone $this;
145 12
        $new->items = $value;
146 12
        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 2
    public function options(array $value): self
159
    {
160 2
        $new = clone $this;
161 2
        $new->options = $value;
162 2
        return $new;
163
    }
164
165
    /**
166
     * The HTML attributes for the items widget.
167
     *
168
     * @param array $value
169
     *
170
     * @return self
171
     *
172
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
173
     */
174 1
    public function itemsOptions(array $value): self
175
    {
176 1
        $new = clone $this;
177 1
        $new->itemsOptions = $value;
178 1
        return $new;
179
    }
180
181 11
    private function buildOptions(): void
182
    {
183 11
        if (!isset($this->options['id'])) {
184 11
            $this->options['id'] = "{$this->getId()}-breadcrumbs";
185
        }
186
187 11
        $this->options = $this->addOptions(
188 11
            array_merge(
189 11
                $this->options,
190 11
                ['aria-label' => 'breadcrumbs']
191
            ),
192 11
            'breadcrumb'
193
        );
194 11
    }
195
196 11
    private function renderIcon(string $icon, array $iconOptions): string
197
    {
198 11
        $html = '';
199
200 11
        if ($icon !== '') {
201 1
            $html = Html::beginTag('span', $iconOptions) .
202 1
                Html::tag('i', '', ['class' => $icon]) .
203 1
                Html::endTag('span');
204
        }
205
206 11
        return $html;
207
    }
208
209
    /**
210
     * Renders a single breadcrumb item.
211
     *
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
     * link.
215
     *
216
     * @throws \InvalidArgumentException|JsonException if `$link` does not have "label" element.
217
     *
218
     * @return string the rendering result
219
     */
220 11
    private function renderItem(array $link, string $template): string
221
    {
222 11
        $encodeLabel = ArrayHelper::remove($link, 'encode', $this->encodeLabels);
223
224 11
        $icon = '';
225 11
        $iconOptions = [];
226
227 11
        if (isset($link['icon'])) {
228 1
            $icon = $link['icon'];
229
        }
230
231 11
        if (isset($link['iconOptions']) && is_array($link['iconOptions'])) {
232 1
            $iconOptions = $this->addOptions($iconOptions, 'icon');
233
        }
234
235 11
        unset($link['icon'], $link['iconOptions']);
236
237 11
        if (array_key_exists('label', $link)) {
238 11
            $label = $encodeLabel ? Html::encode($link['label']) : $link['label'];
239
        } else {
240 1
            throw new \InvalidArgumentException('The "label" element is required for each link.');
241
        }
242
243 11
        if (isset($link['template'])) {
244 1
            $template = $link['template'];
245
        }
246
247 11
        if (isset($link['url'])) {
248 11
            $options = $link;
249 11
            unset($options['template'], $options['label'], $options['url'], $options['icon']);
250 11
            $linkHtml = Html::a($label, $link['url'], $options);
251
        } else {
252 2
            $linkHtml = $label;
253
        }
254
255 11
        return strtr(
256 11
            $template,
257 11
            ['{label}' => $label, '{link}' => $linkHtml, '{icon}' => $this->renderIcon($icon, $iconOptions)]
258
        );
259
    }
260
261 11
    private function renderItems(): array
262
    {
263 11
        $links = [];
264
265 11
        if ($this->withoutHomeItem === false) {
266 11
            $links[] = $this->renderHomeLink();
267
        }
268
269 11
        foreach ($this->items as $link) {
270 11
            if (!is_array($link)) {
271 1
                $link = ['label' => $link];
272
            }
273
274 11
            $links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate);
275
        }
276
277 10
        return $links;
278
    }
279
280 11
    private function renderHomeLink(): string
281
    {
282 11
        if ($this->homeItem === []) {
283 5
            $this->homeItem = ['label' => 'Home', 'url' => '/'];
284
        }
285
286 11
        return $this->renderItem($this->homeItem, $this->itemTemplate);
287
    }
288
}
289