Passed
Push — master ( 8a8b7b...c348f9 )
by Evgeniy
03:48
created

Breadcrumbs   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 229
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 60
c 1
b 0
f 0
dl 0
loc 229
ccs 61
cts 61
cp 1
rs 10
wmc 21

9 Methods

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