Passed
Pull Request — master (#31)
by Wilmer
02:00
created

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