GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 2cbddd...710e06 )
by Sebastian
02:19
created

Menu::render()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 22
rs 8.9197
cc 4
eloc 13
nc 2
nop 0
1
<?php
2
3
namespace Spatie\Menu;
4
5
use Countable;
6
use Spatie\HtmlElement\HtmlElement;
7
use Spatie\Menu\Helpers\Arr;
8
use Spatie\Menu\Helpers\Reflection;
9
use Spatie\Menu\Helpers\Url;
10
use Spatie\Menu\Traits\HtmlAttributes;
11
use Spatie\Menu\Traits\ParentAttributes;
12
13
class Menu implements Item, Countable, HasHtmlAttributes, HasParentAttributes
14
{
15
    use HtmlAttributes, ParentAttributes;
16
17
    /** @var array */
18
    protected $items = [];
19
20
    /** @var string */
21
    protected $prepend = '';
22
23
    /** @var string */
24
    protected $append = '';
25
26
    /** @var array */
27
    protected $filters = [];
28
29
    /** @var array */
30
    protected $wrap = [];
31
32
    /** @var string */
33
    protected $activeClass = 'active';
34
35
    /**
36
     * @param \Spatie\Menu\Item[] ...$items
37
     */
38
    protected function __construct(Item ...$items)
39
    {
40
        $this->items = $items;
41
42
        $this->initializeHtmlAttributes();
43
        $this->initializeParentAttributes();
44
    }
45
46
    /**
47
     * Create a new menu, optionally prefilled with items.
48
     *
49
     * @param array $items
50
     *
51
     * @return static
52
     */
53
    public static function new(array $items = [])
54
    {
55
        return new static(...array_values($items));
56
    }
57
58
    /**
59
     * Add an item to the menu. This also applies all registered filters to the
60
     * item.
61
     *
62
     * @param \Spatie\Menu\Item $item
63
     *
64
     * @return $this
65
     */
66
    public function add(Item $item)
67
    {
68
        foreach ($this->filters as $filter) {
69
            $this->applyFilter($filter, $item);
70
        }
71
72
        $this->items[] = $item;
73
74
        return $this;
75
    }
76
77
    /**
78
     * Add an item to the menu if a (non-strict) condition is met.
79
     *
80
     * @param bool              $condition
81
     * @param \Spatie\Menu\Item $item
82
     *
83
     * @return $this
84
     */
85
    public function addIf($condition, Item $item)
86
    {
87
        if ($condition) {
88
            $this->add($item);
89
        }
90
91
        return $this;
92
    }
93
94
    /**
95
     * Shortcut function to add a plain link to the menu.
96
     *
97
     * @param string $url
98
     * @param string $text
99
     *
100
     * @return $this
101
     */
102
    public function link(string $url, string $text)
103
    {
104
        return $this->add(Link::to($url, $text));
105
    }
106
107
    /**
108
     * Add a link to the menu if a (non-strict) condition is met.
109
     *
110
     * @param bool   $condition
111
     * @param string $url
112
     * @param string $text
113
     *
114
     * @return $this
115
     */
116
    public function linkIf($condition, string $url, string $text)
117
    {
118
        if ($condition) {
119
            $this->link($url, $text);
120
        }
121
122
        return $this;
123
    }
124
125
    /**
126
     * Shortcut function to add raw html to the menu.
127
     *
128
     * @param string $html
129
     *
130
     * @return $this
131
     */
132
    public function html(string $html)
133
    {
134
        return $this->add(Html::raw($html));
135
    }
136
137
    /**
138
     * Add a chunk of html if a (non-strict) condition is met.
139
     *
140
     * @param bool   $condition
141
     * @param string $html
142
     *
143
     * @return $this
144
     */
145
    public function htmlIf($condition, string $html)
146
    {
147
        if ($condition) {
148
            $this->html($html);
149
        }
150
151
        return $this;
152
    }
153
154
    /**
155
     * Add an empty item with parent attributes.
156
     *
157
     * @param array $parentAttributes
158
     *
159
     * @return $this
160
     */
161
    public function void(array $parentAttributes = [])
162
    {
163
        return $this->add(Html::raw('')->setParentAttributes($parentAttributes));
164
    }
165
166
    /**
167
     * Add an empty item with parent attributes if a (non-strict) condition is met.
168
     *
169
     * @param $condition
170
     * @param array $parentAttributes
171
     *
172
     * @return $this
173
     */
174
    public function voidIf($condition, array $parentAttributes = [])
175
    {
176
        if ($condition) {
177
            $this->void($parentAttributes);
178
        }
179
180
        return $this;
181
    }
182
183
    /**
184
     * @param callable|\Spatie\Menu\Menu|\Spatie\Menu\Item $header
185
     * @param callable|\Spatie\Menu\Menu|null $menu
186
     *
187
     * @return $this
188
     */
189
    public function submenu($header, $menu = null)
0 ignored issues
show
Unused Code introduced by
The parameter $header is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $menu is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
190
    {
191
        list($header, $menu) = $this->parseSubmenuArgs(func_get_args());
192
193
        if (is_callable($menu)) {
194
            $transformer = $menu;
195
            $menu = $this->blueprint();
196
            $transformer($menu);
197
        }
198
199
        if ($header instanceof Item) {
200
            $header = $header->render();
201
        }
202
203
        return $this->add($menu->prependIf($header, $header));
204
    }
205
206
    protected function parseSubmenuArgs($args): array
207
    {
208
        if (count($args) === 1) {
209
            return ['', $args[0]];
210
        }
211
212
        return [$args[0], $args[1]];
213
    }
214
215
    /**
216
     * Iterate over all the items and apply a callback. If you typehint the
217
     * item parameter in the callable, it wil only be applied to items of that
218
     * type.
219
     *
220
     * @param callable $callable
221
     *
222
     * @return $this
223
     */
224
    public function each(callable $callable)
225
    {
226
        $type = Reflection::firstParameterType($callable);
227
228
        foreach ($this->items as $item) {
229
            if (!Reflection::itemMatchesType($item, $type)) {
230
                continue;
231
            }
232
233
            $callable($item);
234
        }
235
236
        return $this;
237
    }
238
239
    /**
240
     * Register a filter to the menu. When an item is added, all filters will be
241
     * applied to the item. If you typehint the item parameter in the callable, it
242
     * will only be applied to items of that type.
243
     *
244
     * @param callable $callable
245
     *
246
     * @return $this
247
     */
248
    public function registerFilter(callable $callable)
249
    {
250
        $this->filters[] = $callable;
251
252
        return $this;
253
    }
254
255
    /**
256
     * Apply a filter to an item. Returns the result of the filter.
257
     *
258
     * @param callable          $filter
259
     * @param \Spatie\Menu\Item $item
260
     */
261
    protected function applyFilter(callable $filter, Item $item)
262
    {
263
        $type = Reflection::firstParameterType($filter);
264
265
        if (!Reflection::itemMatchesType($item, $type)) {
266
            return;
267
        }
268
269
        $filter($item);
270
    }
271
272
    /**
273
     * Apply a callable to all existing items, and register it as a filter so it
274
     * will get applied to all new items too. If you typehint the item parameter
275
     * in the callable, it wil only be applied to items of that type.
276
     *
277
     * @param callable $callable
278
     *
279
     * @return $this
280
     */
281
    public function applyToAll(callable $callable)
282
    {
283
        $this->each($callable);
284
        $this->registerFilter($callable);
285
286
        return $this;
287
    }
288
289
    /**
290
     * Prefix all the links in the menu.
291
     *
292
     * @param string $prefix
293
     *
294
     * @return $this
295
     */
296
    public function prefixLinks(string $prefix)
297
    {
298
        return $this->applyToAll(function (Link $link) use ($prefix) {
299
            $link->prefix($prefix);
300
        });
301
    }
302
303
    /**
304
     * Prepend the menu with a string of html on render.
305
     *
306
     * @param string $prepend
307
     *
308
     * @return $this
309
     */
310
    public function prepend(string $prepend)
311
    {
312
        $this->prepend = $prepend;
313
314
        return $this;
315
    }
316
317
    /**
318
     * Prepend the menu with a string of html on render if a certain condition is
319
     * met.
320
     *
321
     * @param bool   $condition
322
     * @param string $prepend
323
     *
324
     * @return $this
325
     */
326
    public function prependIf(bool $condition, string $prepend)
327
    {
328
        if ($condition) {
329
            return $this->prepend($prepend);
330
        }
331
332
        return $this;
333
    }
334
335
    /**
336
     * Append a string of html to the menu on render.
337
     *
338
     * @param string $append
339
     *
340
     * @return $this
341
     */
342
    public function append(string $append)
343
    {
344
        $this->append = $append;
345
346
        return $this;
347
    }
348
349
    /**
350
     * Append the menu with a string of html on render if a certain condition is
351
     * met.
352
     *
353
     * @param bool   $condition
354
     * @param string $append
355
     *
356
     * @return static
357
     */
358
    public function appendIf(bool $condition, string $append)
359
    {
360
        if ($condition) {
361
            return $this->append($append);
362
        }
363
364
        return $this;
365
    }
366
367
    /**
368
     * Wrap the menu in an html element.
369
     *
370
     * @param string $element
371
     * @param array $attributes
372
     *
373
     * @return $this
374
     */
375
    public function wrap(string $element, $attributes = [])
376
    {
377
        $this->wrap = [$element, $attributes];
378
379
        return $this;
380
    }
381
382
    /**
383
     * Determine whether the menu is active.
384
     *
385
     * @return bool
386
     */
387
    public function isActive(): bool
388
    {
389
        foreach ($this->items as $item) {
390
            if ($item->isActive()) {
391
                return true;
392
            }
393
        }
394
395
        return false;
396
    }
397
398
    /**
399
     * Set multiple items in the menu as active based on a callable that filters
400
     * through items. If you typehint the item parameter in the callable, it will
401
     * only be applied to items of that type.
402
     *
403
     * @param callable|string $urlOrCallable
404
     * @param string          $root
405
     *
406
     * @return $this
407
     */
408
    public function setActive($urlOrCallable, string $root = '/')
409
    {
410
        if (is_string($urlOrCallable)) {
411
            return $this->setActiveFromUrl($urlOrCallable, $root);
412
        }
413
414
        if (is_callable($urlOrCallable)) {
415
            return $this->setActiveFromCallable($urlOrCallable);
416
        }
417
418
        throw new \InvalidArgumentException('`setActive` requires a pattern or a callable');
419
    }
420
421
    /**
422
     * Set all relevant children active based on the current request's URL.
423
     *
424
     * /, /about, /contact => request to /about will set the about link active.
425
     *
426
     * /en, /en/about, /en/contact => request to /en won't set /en active if the
427
     *                                request root is set to /en.
428
     *
429
     * @param string $url  The current request url.
430
     * @param string $root If the link's URL is an exact match with the request
431
     *                     root, the link won't be set active. This behavior is
432
     *                     to avoid having home links active on every request.
433
     *
434
     * @return $this
435
     */
436
    public function setActiveFromUrl(string $url, string $root = '/')
437
    {
438
        $this->applyToAll(function (Menu $menu) use ($url, $root) {
439
            $menu->setActiveFromUrl($url, $root);
440
        });
441
442
        $requestUrl = Url::parts($url);
443
        $requestRoot = Url::stripTrailingSlashes($root, '/');
0 ignored issues
show
Unused Code introduced by
The call to Url::stripTrailingSlashes() has too many arguments starting with '/'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
444
445
        $this->applyToAll(function ($item) use ($requestUrl, $requestRoot) {
446
447
            // Not using a magic typehint since we need to do two instance checks
448
            if (!$item instanceof HasUrl || !$item instanceof Activatable) {
449
                return;
450
            }
451
452
            $url = Url::parts($item->getUrl());
453
454
            // If the menu item is on a different host it can't be active.
455
            if ($url['host'] !== '' && $url['host'] !== $requestUrl['host']) {
456
                return;
457
            }
458
459
            // If the request url or the link url is on the root, only set exact matches active.
460
            if (
461
                $requestUrl['path'] === $requestRoot ||
462
                $url['path'] === $requestRoot
463
            ) {
464
                if ($url['path'] === $requestUrl['path']) {
465
                    $item->setActive();
466
                }
467
468
                return;
469
            }
470
471
            // If the request path is empty and it isn't the root, there's most likely a
472
            // configuration error, and the item isn't active.
473
            if (empty($url['path'])) {
474
                return;
475
            }
476
477
            // The menu item is active if it's path starts with the request path.
478
            if (strpos($requestUrl['path'], $url['path']) === 0) {
479
                $item->setActive();
480
            };
481
        });
482
483
        return $this;
484
    }
485
486
    /**
487
     * @param callable $callable
488
     *
489
     * @return $this
490
     */
491
    public function setActiveFromCallable(callable $callable)
492
    {
493
        $this->applyToAll(function (Menu $menu) use ($callable) {
494
            $menu->setActiveFromCallable($callable);
495
        });
496
497
        $type = Reflection::firstParameterType($callable);
498
499
        $this->applyToAll(function (Activatable $item) use ($callable, $type) {
500
501
            if (!Reflection::itemMatchesType($item, $type)) {
0 ignored issues
show
Documentation introduced by
$item is of type object<Spatie\Menu\Activatable>, but the function expects a object<Spatie\Menu\Item>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
502
                return;
503
            }
504
505
            if ($callable($item)) {
506
                $item->setActive();
507
            }
508
        });
509
510
        return $this;
511
    }
512
513
    /**
514
     * Set the class name that will be used on active items for this menu.
515
     *
516
     * @param string $class
517
     *
518
     * @return $this
519
     */
520
    public function setActiveClass(string $class)
521
    {
522
        $this->activeClass = $class;
523
524
        return $this;
525
    }
526
527
    /**
528
     * Add a class to all items in the menu.
529
     *
530
     * @param string $class
531
     *
532
     * @return $this
533
     */
534
    public function addItemClass(string $class)
535
    {
536
        $this->applyToAll(function (HasHtmlAttributes $link) use ($class) {
537
            $link->addClass($class);
538
        });
539
540
        return $this;
541
    }
542
543
    /**
544
     * Set an attribute on all items in the menu.
545
     *
546
     * @param string $attribute
547
     * @param string $value
548
     *
549
     * @return $this
550
     */
551
    public function setItemAttribute(string $attribute, string $value = '')
552
    {
553
        $this->applyToAll(function (HasHtmlAttributes $link) use ($attribute, $value) {
554
            $link->setAttribute($attribute, $value);
555
        });
556
557
        return $this;
558
    }
559
560
    /**
561
     * Add a parent class to all items in the menu.
562
     *
563
     * @param string $class
564
     *
565
     * @return $this
566
     */
567
    public function addItemParentClass(string $class)
568
    {
569
        $this->applyToAll(function (HasParentAttributes $item) use ($class) {
570
            $item->addParentClass($class);
571
        });
572
573
        return $this;
574
    }
575
576
    /**
577
     * Add a parent attribute to all items in the menu.
578
     *
579
     * @param string $attribute
580
     * @param string $value
581
     *
582
     * @return $this
583
     */
584
    public function setItemParentAttribute(string $attribute, string $value = '')
585
    {
586
        $this->applyToAll(function (HasParentAttributes $item) use ($attribute, $value) {
587
            $item->setParentAttribute($attribute, $value);
588
        });
589
590
        return $this;
591
    }
592
593
    /**
594
     * Create a empty blueprint of the menu (copies `filters` and `activeClass`).
595
     *
596
     * @return static
597
     */
598
    public function blueprint()
599
    {
600
        $clone = new static();
601
602
        $clone->filters = $this->filters;
603
        $clone->activeClass = $this->activeClass;
604
605
        return $clone;
606
    }
607
608
    /**
609
     * Render the menu.
610
     *
611
     * @return string
612
     */
613
    public function render(): string
614
    {
615
        $contents = HtmlElement::render(
616
            'ul',
617
            $this->htmlAttributes->toArray(),
618
            Arr::map($this->items, function (Item $item) {
619
                return HtmlElement::render(
620
                    $item->isActive() ? "li.{$this->activeClass}" : 'li',
621
                    $item instanceof HasParentAttributes ? $item->getParentAttributes() : [],
622
                    $item->render()
623
                );
624
            })
625
        );
626
627
        $menu = "{$this->prepend}{$contents}{$this->append}";
628
629
        if (!empty($this->wrap)) {
630
            return HtmlElement::render($this->wrap[0], $this->wrap[1], $menu);
631
        }
632
633
        return $menu;
634
    }
635
636
    /**
637
     * The amount of items in the menu.
638
     *
639
     * @return int
640
     */
641
    public function count(): int
642
    {
643
        return count($this->items);
644
    }
645
646
    /**
647
     * @return string
648
     */
649
    public function __toString(): string
650
    {
651
        return $this->render();
652
    }
653
}
654