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 ( 776c95...f38163 )
by Sebastian
03:04
created

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