Passed
Push — master ( e80b4e...741461 )
by Alexander
02:19
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 $encodeLinks = 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 13
    protected function run(): string
42
    {
43 13
        if (empty($this->items)) {
44 1
            return '';
45
        }
46
47 12
        $this->buildOptions();
48
49
        return
50 12
            Html::beginTag('nav', $this->options) . "\n" .
51 12
                Html::beginTag('ul', $this->itemsOptions) . "\n" .
52 12
                    implode('', $this->renderItems()) .
53 11
                Html::endTag('ul') . "\n" .
54 11
            Html::endTag('nav');
55
    }
56
57
    /**
58
     * Disable encoding for labels.
59
     *
60
     * @return $this
61
     */
62 1
    public function withoutEncodeLabels(): self
63
    {
64 1
        $new = clone $this;
65 1
        $new->encodeLabels = false;
66 1
        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 13
    public function items(array $value): self
144
    {
145 13
        $new = clone $this;
146 13
        $new->items = $value;
147 13
        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 12
    private function buildOptions(): void
183
    {
184 12
        if (!isset($this->options['id'])) {
185 12
            $this->options['id'] = "{$this->getId()}-breadcrumbs";
186
        }
187
188 12
        $this->options = $this->addOptions(
189 12
            array_merge(
190 12
                $this->options,
191 12
                ['aria-label' => 'breadcrumbs']
192
            ),
193 12
            'breadcrumb'
194
        );
195 12
    }
196
197 12
    private function renderIcon(string $icon, array $iconOptions): string
198
    {
199 12
        $html = '';
200
201 12
        if ($icon !== '') {
202 1
            $html = Html::beginTag('span', $iconOptions) .
203 1
                Html::tag('i', '', ['class' => $icon]) .
204 1
                Html::endTag('span');
205
        }
206
207 12
        return $html;
208
    }
209
210
    /**
211
     * Renders a single breadcrumb item.
212
     *
213
     * @param array $link the link to be rendered. It must contain the "label" element. The "url" element is optional.
214
     * @param string $template the template to be used to rendered the link. The token "{link}" will be replaced by the
215
     * link.
216
     *
217
     * @throws InvalidArgumentException|JsonException if `$link` does not have "label" element.
218
     *
219
     * @return string the rendering result
220
     */
221 12
    private function renderItem(array $link, string $template): string
222
    {
223 12
        $encodeLabel = ArrayHelper::remove($link, 'encode', $this->encodeLabels);
224
225 12
        $icon = '';
226 12
        $iconOptions = [];
227
228 12
        if (isset($link['icon'])) {
229 1
            $icon = $link['icon'];
230
        }
231
232 12
        if (isset($link['iconOptions']) && is_array($link['iconOptions'])) {
233 1
            $iconOptions = $this->addOptions($iconOptions, 'icon');
234
        }
235
236 12
        unset($link['icon'], $link['iconOptions']);
237
238 12
        if (array_key_exists('label', $link)) {
239 12
            $label = $encodeLabel ? Html::encode($link['label']) : $link['label'];
240
        } else {
241 1
            throw new InvalidArgumentException('The "label" element is required for each link.');
242
        }
243
244 12
        if (isset($link['template'])) {
245 1
            $template = $link['template'];
246
        }
247
248 12
        if ($this->encodeLinks === false) {
249 12
            $link['encode'] = false;
250
        }
251
252 12
        if (isset($link['url'])) {
253 12
            $options = $link;
254 12
            unset($options['template'], $options['label'], $options['url'], $options['icon']);
255 12
            $linkHtml = Html::a($label, $link['url'], $options);
256
        } else {
257 2
            $linkHtml = $label;
258
        }
259
260 12
        return strtr(
261 12
            $template,
262 12
            ['{label}' => $label, '{link}' => $linkHtml, '{icon}' => $this->renderIcon($icon, $iconOptions)]
263
        );
264
    }
265
266 12
    private function renderItems(): array
267
    {
268 12
        $links = [];
269
270 12
        if ($this->withoutHomeItem === false) {
271 11
            $links[] = $this->renderHomeLink();
272
        }
273
274 12
        foreach ($this->items as $link) {
275 12
            if (!is_array($link)) {
276 1
                $link = ['label' => $link];
277
            }
278
279 12
            $links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate);
280
        }
281
282 11
        return $links;
283
    }
284
285 11
    private function renderHomeLink(): string
286
    {
287 11
        if ($this->homeItem === []) {
288 5
            $this->homeItem = ['label' => 'Home', 'url' => '/'];
289
        }
290
291 11
        return $this->renderItem($this->homeItem, $this->itemTemplate);
292
    }
293
}
294