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 ( 7e340c...77c706 )
by Sebastian
14:33
created

Menu::__toString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
rs 10
cc 1
eloc 2
nc 1
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
        $menu = $this->createSubmenuMenu($menu);
194
        $header = $this->createSubmenuHeader($header);
195
196
        return $this->add($menu->prependIf($header, $header));
0 ignored issues
show
Documentation introduced by
$header is of type string, but the function expects a boolean.

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...
197
    }
198
199
    protected function parseSubmenuArgs($args): array
200
    {
201
        if (count($args) === 1) {
202
            return ['', $args[0]];
203
        }
204
205
        return [$args[0], $args[1]];
206
    }
207
208
    /**
209
     * @param \Spatie\Menu\Menu|callable $menu
210
     *
211
     * @return \Spatie\Menu\Menu
212
     */
213
    protected function createSubmenuMenu($menu): Menu
214
    {
215
        if (is_callable($menu)) {
216
            $transformer = $menu;
217
            $menu = $this->blueprint();
218
            $transformer($menu);
219
        }
220
221
        return $menu;
222
    }
223
224
    /**
225
     * @param \Spatie\Menu\Item|string $header
226
     *
227
     * @return string
228
     */
229
    protected function createSubmenuHeader($header): string
230
    {
231
        if ($header instanceof Item) {
232
            $header = $header->render();
233
        }
234
235
        return $header;
236
    }
237
238
    /**
239
     * Iterate over all the items and apply a callback. If you typehint the
240
     * item parameter in the callable, it wil only be applied to items of that
241
     * type.
242
     *
243
     * @param callable $callable
244
     *
245
     * @return $this
246
     */
247
    public function each(callable $callable)
248
    {
249
        $type = Reflection::firstParameterType($callable);
250
251
        foreach ($this->items as $item) {
252
            if (!Reflection::itemMatchesType($item, $type)) {
253
                continue;
254
            }
255
256
            $callable($item);
257
        }
258
259
        return $this;
260
    }
261
262
    /**
263
     * Register a filter to the menu. When an item is added, all filters will be
264
     * applied to the item. If you typehint the item parameter in the callable, it
265
     * will only be applied to items of that type.
266
     *
267
     * @param callable $callable
268
     *
269
     * @return $this
270
     */
271
    public function registerFilter(callable $callable)
272
    {
273
        $this->filters[] = $callable;
274
275
        return $this;
276
    }
277
278
    /**
279
     * Apply a filter to an item. Returns the result of the filter.
280
     *
281
     * @param callable          $filter
282
     * @param \Spatie\Menu\Item $item
283
     */
284
    protected function applyFilter(callable $filter, Item $item)
285
    {
286
        $type = Reflection::firstParameterType($filter);
287
288
        if (!Reflection::itemMatchesType($item, $type)) {
289
            return;
290
        }
291
292
        $filter($item);
293
    }
294
295
    /**
296
     * Apply a callable to all existing items, and register it as a filter so it
297
     * will get applied to all new items too. If you typehint the item parameter
298
     * in the callable, it wil only be applied to items of that type.
299
     *
300
     * @param callable $callable
301
     *
302
     * @return $this
303
     */
304
    public function applyToAll(callable $callable)
305
    {
306
        $this->each($callable);
307
        $this->registerFilter($callable);
308
309
        return $this;
310
    }
311
312
    /**
313
     * Prefix all the links in the menu.
314
     *
315
     * @param string $prefix
316
     *
317
     * @return $this
318
     */
319
    public function prefixLinks(string $prefix)
320
    {
321
        return $this->applyToAll(function (Link $link) use ($prefix) {
322
            $link->prefix($prefix);
323
        });
324
    }
325
326
    /**
327
     * Prepend the menu with a string of html on render.
328
     *
329
     * @param string $prepend
330
     *
331
     * @return $this
332
     */
333
    public function prepend(string $prepend)
334
    {
335
        $this->prepend = $prepend;
336
337
        return $this;
338
    }
339
340
    /**
341
     * Prepend the menu with a string of html on render if a certain condition is
342
     * met.
343
     *
344
     * @param bool   $condition
345
     * @param string $prepend
346
     *
347
     * @return $this
348
     */
349
    public function prependIf(bool $condition, string $prepend)
350
    {
351
        if ($condition) {
352
            return $this->prepend($prepend);
353
        }
354
355
        return $this;
356
    }
357
358
    /**
359
     * Append a string of html to the menu on render.
360
     *
361
     * @param string $append
362
     *
363
     * @return $this
364
     */
365
    public function append(string $append)
366
    {
367
        $this->append = $append;
368
369
        return $this;
370
    }
371
372
    /**
373
     * Append the menu with a string of html on render if a certain condition is
374
     * met.
375
     *
376
     * @param bool   $condition
377
     * @param string $append
378
     *
379
     * @return static
380
     */
381
    public function appendIf(bool $condition, string $append)
382
    {
383
        if ($condition) {
384
            return $this->append($append);
385
        }
386
387
        return $this;
388
    }
389
390
    /**
391
     * Wrap the menu in an html element.
392
     *
393
     * @param string $element
394
     * @param array $attributes
395
     *
396
     * @return $this
397
     */
398
    public function wrap(string $element, $attributes = [])
399
    {
400
        $this->wrap = [$element, $attributes];
401
402
        return $this;
403
    }
404
405
    /**
406
     * Determine whether the menu is active.
407
     *
408
     * @return bool
409
     */
410
    public function isActive(): bool
411
    {
412
        foreach ($this->items as $item) {
413
            if ($item->isActive()) {
414
                return true;
415
            }
416
        }
417
418
        return false;
419
    }
420
421
    /**
422
     * Set multiple items in the menu as active based on a callable that filters
423
     * through items. If you typehint the item parameter in the callable, it will
424
     * only be applied to items of that type.
425
     *
426
     * @param callable|string $urlOrCallable
427
     * @param string          $root
428
     *
429
     * @return $this
430
     */
431
    public function setActive($urlOrCallable, string $root = '/')
432
    {
433
        if (is_string($urlOrCallable)) {
434
            return $this->setActiveFromUrl($urlOrCallable, $root);
435
        }
436
437
        if (is_callable($urlOrCallable)) {
438
            return $this->setActiveFromCallable($urlOrCallable);
439
        }
440
441
        throw new \InvalidArgumentException('`setActive` requires a pattern or a callable');
442
    }
443
444
    /**
445
     * Set all relevant children active based on the current request's URL.
446
     *
447
     * /, /about, /contact => request to /about will set the about link active.
448
     *
449
     * /en, /en/about, /en/contact => request to /en won't set /en active if the
450
     *                                request root is set to /en.
451
     *
452
     * @param string $url  The current request url.
453
     * @param string $root If the link's URL is an exact match with the request
454
     *                     root, the link won't be set active. This behavior is
455
     *                     to avoid having home links active on every request.
456
     *
457
     * @return $this
458
     */
459
    public function setActiveFromUrl(string $url, string $root = '/')
460
    {
461
        $this->applyToAll(function (Menu $menu) use ($url, $root) {
462
            $menu->setActiveFromUrl($url, $root);
463
        });
464
465
        $requestUrl = Url::parts($url);
466
        $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...
467
468
        $this->applyToAll(function ($item) use ($requestUrl, $requestRoot) {
469
470
            // Not using a magic typehint since we need to do two instance checks
471
            if (!$item instanceof HasUrl || !$item instanceof Activatable) {
472
                return;
473
            }
474
475
            $url = Url::parts($item->getUrl());
476
477
            // If the menu item is on a different host it can't be active.
478
            if ($url['host'] !== '' && $url['host'] !== $requestUrl['host']) {
479
                return;
480
            }
481
482
            // If the request url or the link url is on the root, only set exact matches active.
483
            if (
484
                $requestUrl['path'] === $requestRoot ||
485
                $url['path'] === $requestRoot
486
            ) {
487
                if ($url['path'] === $requestUrl['path']) {
488
                    $item->setActive();
489
                }
490
491
                return;
492
            }
493
494
            // If the request path is empty and it isn't the root, there's most likely a
495
            // configuration error, and the item isn't active.
496
            if (empty($url['path'])) {
497
                return;
498
            }
499
500
            // The menu item is active if it's path starts with the request path.
501
            if (strpos($requestUrl['path'], $url['path']) === 0) {
502
                $item->setActive();
503
            };
504
        });
505
506
        return $this;
507
    }
508
509
    /**
510
     * @param callable $callable
511
     *
512
     * @return $this
513
     */
514
    public function setActiveFromCallable(callable $callable)
515
    {
516
        $this->applyToAll(function (Menu $menu) use ($callable) {
517
            $menu->setActiveFromCallable($callable);
518
        });
519
520
        $type = Reflection::firstParameterType($callable);
521
522
        $this->applyToAll(function (Activatable $item) use ($callable, $type) {
523
524
            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...
525
                return;
526
            }
527
528
            if ($callable($item)) {
529
                $item->setActive();
530
            }
531
        });
532
533
        return $this;
534
    }
535
536
    /**
537
     * Set the class name that will be used on active items for this menu.
538
     *
539
     * @param string $class
540
     *
541
     * @return $this
542
     */
543
    public function setActiveClass(string $class)
544
    {
545
        $this->activeClass = $class;
546
547
        return $this;
548
    }
549
550
    /**
551
     * Add a class to all items in the menu.
552
     *
553
     * @param string $class
554
     *
555
     * @return $this
556
     */
557
    public function addItemClass(string $class)
558
    {
559
        $this->applyToAll(function (HasHtmlAttributes $link) use ($class) {
560
            $link->addClass($class);
561
        });
562
563
        return $this;
564
    }
565
566
    /**
567
     * Set an attribute on all items in the menu.
568
     *
569
     * @param string $attribute
570
     * @param string $value
571
     *
572
     * @return $this
573
     */
574
    public function setItemAttribute(string $attribute, string $value = '')
575
    {
576
        $this->applyToAll(function (HasHtmlAttributes $link) use ($attribute, $value) {
577
            $link->setAttribute($attribute, $value);
578
        });
579
580
        return $this;
581
    }
582
583
    /**
584
     * Add a parent class to all items in the menu.
585
     *
586
     * @param string $class
587
     *
588
     * @return $this
589
     */
590
    public function addItemParentClass(string $class)
591
    {
592
        $this->applyToAll(function (HasParentAttributes $item) use ($class) {
593
            $item->addParentClass($class);
594
        });
595
596
        return $this;
597
    }
598
599
    /**
600
     * Add a parent attribute to all items in the menu.
601
     *
602
     * @param string $attribute
603
     * @param string $value
604
     *
605
     * @return $this
606
     */
607
    public function setItemParentAttribute(string $attribute, string $value = '')
608
    {
609
        $this->applyToAll(function (HasParentAttributes $item) use ($attribute, $value) {
610
            $item->setParentAttribute($attribute, $value);
611
        });
612
613
        return $this;
614
    }
615
616
    /**
617
     * Create a empty blueprint of the menu (copies `filters` and `activeClass`).
618
     *
619
     * @return static
620
     */
621
    public function blueprint()
622
    {
623
        $clone = new static();
624
625
        $clone->filters = $this->filters;
626
        $clone->activeClass = $this->activeClass;
627
628
        return $clone;
629
    }
630
631
    /**
632
     * Render the menu.
633
     *
634
     * @return string
635
     */
636
    public function render(): string
637
    {
638
        $contents = HtmlElement::render(
639
            'ul',
640
            $this->htmlAttributes->toArray(),
641
            Arr::map($this->items, function (Item $item) {
642
                return HtmlElement::render(
643
                    $item->isActive() ? "li.{$this->activeClass}" : 'li',
644
                    $item instanceof HasParentAttributes ? $item->getParentAttributes() : [],
645
                    $item->render()
646
                );
647
            })
648
        );
649
650
        $menu = "{$this->prepend}{$contents}{$this->append}";
651
652
        if (!empty($this->wrap)) {
653
            return HtmlElement::render($this->wrap[0], $this->wrap[1], $menu);
654
        }
655
656
        return $menu;
657
    }
658
659
    /**
660
     * The amount of items in the menu.
661
     *
662
     * @return int
663
     */
664
    public function count(): int
665
    {
666
        return count($this->items);
667
    }
668
669
    /**
670
     * @return string
671
     */
672
    public function __toString(): string
673
    {
674
        return $this->render();
675
    }
676
}
677