Passed
Pull Request — master (#53)
by Wilmer
07:05 queued 04:41
created

Breadcrumbs::attributes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
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 6
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Widgets;
6
7
use InvalidArgumentException;
8
use JsonException;
9
use Yiisoft\Html\Html;
10
use Yiisoft\Widget\Widget;
11
12
use function array_key_exists;
13
use function implode;
14
use function is_array;
15
use function strtr;
16
17
/**
18
 * Breadcrumbs displays a list of items indicating the position of the current page in the whole site hierarchy.
19
 *
20
 * For example, breadcrumbs like "Home / Sample Post / Edit" means the user is viewing an edit page for the
21
 * "Sample Post". He can click on "Sample Post" to view that page, or he can click on "Home" to return to the homepage.
22
 *
23
 * To use Breadcrumbs, you need to configure its {@see Breadcrumbs::items()} method,
24
 * which specifies the items to be displayed. For example:
25
 *
26
 * ```php
27
 * // $this is the view object currently being used
28
 * echo Breadcrumbs::widget()
29
 *     -> itemTemplate() => "<li><i>{link}</i></li>\n", // template for all links
30
 *     -> items() => [
31
 *         [
32
 *             'label' => 'Post Category',
33
 *             'url' => 'post-category/view?id=10',
34
 *             'template' => "<li><b>{link}</b></li>\n", // template for this link only
35
 *         ],
36
 *         ['label' => 'Sample Post', 'url' => 'post/edit?id=1',
37
 *         'Edit',
38
 *     ];
39
 * ```
40
 *
41
 * Because breadcrumbs usually appears in nearly every page of a website, you may consider placing it in a layout view.
42
 * You can use a view common parameter (e.g. `$this->getCommonParameter('breadcrumbs')`) to configure the items in
43
 * different views. In the layout view, you assign this view parameter to the {@see Breadcrumbs::items()} method
44
 * like the following:
45
 *
46
 * ```php
47
 * // $this is the view object currently being used
48
 * echo Breadcrumbs::widget()->items($this->getCommonParameter('breadcrumbs', []));
49
 * ```
50
 */
51
final class Breadcrumbs extends Widget
52
{
53
    private string $activeItemTemplate = "<li class=\"active\">{link}</li>\n";
54
    private array $attributes = ['class' => 'breadcrumb'];
55
    private array|null $homeItem = ['label' => 'Home', 'url' => '/'];
56
    private array $items = [];
57
    private string $itemTemplate = "<li>{link}</li>\n";
58
    private string $tag = 'ul';
59
60
    /**
61
     * Returns a new instance with the specified active item template.
62
     *
63
     * @param string $value The template used to render each active item in the breadcrumbs.
64
     * The token `{link}` will be replaced with the actual HTML link for each active item.
65
     *
66
     * @return self
67
     */
68 4
    public function activeItemTemplate(string $value): self
69
    {
70 4
        $new = clone $this;
71 4
        $new->activeItemTemplate = $value;
72
73 4
        return $new;
74
    }
75
76
    /**
77
     * The HTML attributes for the main widget tag.
78
     *
79
     * @param array $value Array of attribute name => attribute value pairs.
80
     *
81
     * @return static
82
     */
83 3
    public function attributes(array $value): self
84
    {
85 3
        $new = clone $this;
86 3
        $new->attributes = $value;
87
88 3
        return $new;
89
    }
90
91
    /**
92
     * Returns a new instance with the specified first item in the breadcrumbs (called home link).
93
     *
94
     * If a null is specified, the home item will not be rendered.
95
     *
96
     * @param array|null $value Please refer to {@see items()} on the format.
97
     *
98
     * @throws InvalidArgumentException If an empty array is specified.
99
     *
100
     * @return self
101
     */
102 8
    public function homeItem(?array $value): self
103
    {
104 8
        if ($value === []) {
105 1
            throw new InvalidArgumentException(
106
                'The home item cannot be an empty array. To disable rendering of the home item, specify null.',
107
            );
108
        }
109
110 7
        $new = clone $this;
111 7
        $new->homeItem = $value;
112
113 7
        return $new;
114
    }
115
116
    /**
117
     * Returns a new instance with the specified list of items.
118
     *
119
     * @param array $value List of items to appear in the breadcrumbs. If this property is empty, the widget will not
120
     * render anything. Each array element represents a single item in the breadcrumbs with the following structure:
121
     *
122
     * ```php
123
     * [
124
     *     'label' => 'label of the item',  // required
125
     *     'url' => 'url of the item',      // optional
126
     *     'template' => 'own template of the item', // optional, if not set $this->itemTemplate will be used
127
     * ]
128
     * ```
129
     *
130
     * If a item is active, you only need to specify its "label", and instead of writing `['label' => $label]`, you may
131
     * simply use `$label`.
132
     *
133
     * Additional array elements for each item will be treated as the HTML attributes for the hyperlink tag.
134
     * For example, the following item specification will generate a hyperlink with CSS class `external`:
135
     *
136
     * ```php
137
     * [
138
     *     'label' => 'demo',
139
     *     'url' => 'http://example.com',
140
     *     'class' => 'external',
141
     * ]
142
     * ```
143
     *
144
     * Each individual item can override global {@see encodeLabels} param like the following:
145
     *
146
     * ```php
147
     * [
148
     *     'label' => '<strong>Hello!</strong>',
149
     *     'encode' => false,
150
     * ]
151
     * ```
152
     *
153
     * @return self
154
     */
155 10
    public function items(array $value): self
156
    {
157 10
        $new = clone $this;
158 10
        $new->items = $value;
159
160 10
        return $new;
161
    }
162
163
    /**
164
     * Returns a new instance with the specified item template.
165
     *
166
     * @param string $value The template used to render each inactive item in the breadcrumbs.
167
     * The token `{link}` will be replaced with the actual HTML link for each inactive item.
168
     *
169
     * @return self
170
     */
171 2
    public function itemTemplate(string $value): self
172
    {
173 2
        $new = clone $this;
174 2
        $new->itemTemplate = $value;
175
176 2
        return $new;
177
    }
178
179
    /**
180
     * Returns a new instance with the specified tag.
181
     *
182
     * @param string $value The tag name.
183
     *
184
     * @return self
185
     */
186 4
    public function tag(string $value): self
187
    {
188 4
        $new = clone $this;
189 4
        $new->tag = $value;
190
191 4
        return $new;
192
    }
193
194
    /**
195
     * Renders the widget.
196
     *
197
     * @throws JsonException
198
     *
199
     * @return string The result of widget execution to be outputted.
200
     */
201 10
    protected function run(): string
202
    {
203 10
        if ($this->items === []) {
204 1
            return '';
205
        }
206
207 9
        $items = [];
208
209 9
        if ($this->homeItem !== null) {
210 4
            $items[] = $this->renderItem($this->homeItem, $this->itemTemplate);
211
        }
212
213 9
        foreach ($this->items as $item) {
214 9
            if (!is_array($item)) {
215 6
                $item = ['label' => $item];
216
            }
217
218 9
            if ($item !== []) {
219 9
                $items[] = $this->renderItem(
220
                    $item,
221 9
                    isset($item['url']) ? $this->itemTemplate : $this->activeItemTemplate
222
                );
223
            }
224
        }
225
226 8
        $body = implode('', $items);
227
228 8
        return empty($this->tag)
229 2
            ? $body
230 8
            : Html::normalTag($this->tag, PHP_EOL . $body, $this->attributes)->encode(false)->render();
231
    }
232
233
    /**
234
     * Renders a single breadcrumb item.
235
     *
236
     * @param array $item The item to be rendered. It must contain the "label" element. The "url" element is optional.
237
     * @param string $template The template to be used to rendered the link. The token "{link}" will be replaced by the
238
     * link.
239
     *
240
     * @throws InvalidArgumentException|JsonException if `$item` does not have "label" element.
241
     *
242
     * @return string The rendering result.
243
     */
244 9
    private function renderItem(array $item, string $template): string
245
    {
246 9
        if (!array_key_exists('label', $item)) {
247 1
            throw new InvalidArgumentException('The "label" element is required for each item.');
248
        }
249
250 8
        $encodeLabel = $item['encode'] ?? true;
251 8
        $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
252
253 8
        if (isset($item['template'])) {
254 1
            $template = $item['template'];
255
        }
256
257 8
        if (isset($item['url'])) {
258 3
            $link = $item['url'];
259 3
            unset($item['template'], $item['label'], $item['url']);
260 3
            $link = Html::a($label, $link, $item);
261
        } else {
262 8
            $link = $label;
263
        }
264
265 8
        return strtr($template, ['{link}' => $link]);
266
    }
267
}
268