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 ( 12ca95...f15694 )
by Sebastian
28:05 queued 08:17
created

Menu::submenuIf()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 3
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
    public function prefixLinks(string $prefix)
336
    {
337
        return $this->applyToAll(function (Link $link) use ($prefix) {
338
            $link->prefix($prefix);
339
        });
340
    }
341
342
    /**
343
     * Prepend the menu with a string of html on render.
344
     *
345
     * @param string $prepend
346
     *
347
     * @return $this
348
     */
349
    public function prepend(string $prepend)
350
    {
351
        $this->prepend = $prepend;
352
353
        return $this;
354
    }
355
356
    /**
357
     * Prepend the menu with a string of html on render if a certain condition is
358
     * met.
359
     *
360
     * @param bool   $condition
361
     * @param string $prepend
362
     *
363
     * @return $this
364
     */
365
    public function prependIf(bool $condition, string $prepend)
366
    {
367
        if ($condition) {
368
            return $this->prepend($prepend);
369
        }
370
371
        return $this;
372
    }
373
374
    /**
375
     * Append a string of html to the menu on render.
376
     *
377
     * @param string $append
378
     *
379
     * @return $this
380
     */
381
    public function append(string $append)
382
    {
383
        $this->append = $append;
384
385
        return $this;
386
    }
387
388
    /**
389
     * Append the menu with a string of html on render if a certain condition is
390
     * met.
391
     *
392
     * @param bool   $condition
393
     * @param string $append
394
     *
395
     * @return static
396
     */
397
    public function appendIf(bool $condition, string $append)
398
    {
399
        if ($condition) {
400
            return $this->append($append);
401
        }
402
403
        return $this;
404
    }
405
406
    /**
407
     * Wrap the menu in an html element.
408
     *
409
     * @param string $element
410
     * @param array $attributes
411
     *
412
     * @return $this
413
     */
414
    public function wrap(string $element, $attributes = [])
415
    {
416
        $this->wrap = [$element, $attributes];
417
418
        return $this;
419
    }
420
421
    /**
422
     * Determine whether the menu is active.
423
     *
424
     * @return bool
425
     */
426
    public function isActive(): bool
427
    {
428
        foreach ($this->items as $item) {
429
            if ($item->isActive()) {
430
                return true;
431
            }
432
        }
433
434
        return false;
435
    }
436
437
    /**
438
     * Set multiple items in the menu as active based on a callable that filters
439
     * through items. If you typehint the item parameter in the callable, it will
440
     * only be applied to items of that type.
441
     *
442
     * @param callable|string $urlOrCallable
443
     * @param string          $root
444
     *
445
     * @return $this
446
     */
447
    public function setActive($urlOrCallable, string $root = '/')
448
    {
449
        if (is_string($urlOrCallable)) {
450
            return $this->setActiveFromUrl($urlOrCallable, $root);
451
        }
452
453
        if (is_callable($urlOrCallable)) {
454
            return $this->setActiveFromCallable($urlOrCallable);
455
        }
456
457
        throw new \InvalidArgumentException('`setActive` requires a pattern or a callable');
458
    }
459
460
    /**
461
     * Set all relevant children active based on the current request's URL.
462
     *
463
     * /, /about, /contact => request to /about will set the about link active.
464
     *
465
     * /en, /en/about, /en/contact => request to /en won't set /en active if the
466
     *                                request root is set to /en.
467
     *
468
     * @param string $url  The current request url.
469
     * @param string $root If the link's URL is an exact match with the request
470
     *                     root, the link won't be set active. This behavior is
471
     *                     to avoid having home links active on every request.
472
     *
473
     * @return $this
474
     */
475
    public function setActiveFromUrl(string $url, string $root = '/')
476
    {
477
        $this->applyToAll(function (Menu $menu) use ($url, $root) {
478
            $menu->setActiveFromUrl($url, $root);
479
        });
480
481
        $requestUrl = Url::parts($url);
482
        $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...
483
484
        $this->applyToAll(function ($item) use ($requestUrl, $requestRoot) {
485
486
            // Not using a magic typehint since we need to do two instance checks
487
            if (!$item instanceof HasUrl || !$item instanceof Activatable) {
488
                return;
489
            }
490
491
            $url = Url::parts($item->getUrl());
492
493
            // If the menu item is on a different host it can't be active.
494
            if ($url['host'] !== '' && $url['host'] !== $requestUrl['host']) {
495
                return;
496
            }
497
498
            // If the request url or the link url is on the root, only set exact matches active.
499
            if (
500
                $requestUrl['path'] === $requestRoot ||
501
                $url['path'] === $requestRoot
502
            ) {
503
                if ($url['path'] === $requestUrl['path']) {
504
                    $item->setActive();
505
                }
506
507
                return;
508
            }
509
510
            // If the request path is empty and it isn't the root, there's most likely a
511
            // configuration error, and the item isn't active.
512
            if (empty($url['path'])) {
513
                return;
514
            }
515
516
            // The menu item is active if it's path starts with the request path.
517
            if (strpos($requestUrl['path'], $url['path']) === 0) {
518
                $item->setActive();
519
            };
520
        });
521
522
        return $this;
523
    }
524
525
    /**
526
     * @param callable $callable
527
     *
528
     * @return $this
529
     */
530
    public function setActiveFromCallable(callable $callable)
531
    {
532
        $this->applyToAll(function (Menu $menu) use ($callable) {
533
            $menu->setActiveFromCallable($callable);
534
        });
535
536
        $type = Reflection::firstParameterType($callable);
537
538
        $this->applyToAll(function (Activatable $item) use ($callable, $type) {
539
540
            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...
541
                return;
542
            }
543
544
            if ($callable($item)) {
545
                $item->setActive();
546
            }
547
        });
548
549
        return $this;
550
    }
551
552
    /**
553
     * Set the class name that will be used on active items for this menu.
554
     *
555
     * @param string $class
556
     *
557
     * @return $this
558
     */
559
    public function setActiveClass(string $class)
560
    {
561
        $this->activeClass = $class;
562
563
        return $this;
564
    }
565
566
    /**
567
     * Add a class to all items in the menu.
568
     *
569
     * @param string $class
570
     *
571
     * @return $this
572
     */
573
    public function addItemClass(string $class)
574
    {
575
        $this->applyToAll(function (HasHtmlAttributes $link) use ($class) {
576
            $link->addClass($class);
577
        });
578
579
        return $this;
580
    }
581
582
    /**
583
     * Set an attribute on all items in the menu.
584
     *
585
     * @param string $attribute
586
     * @param string $value
587
     *
588
     * @return $this
589
     */
590
    public function setItemAttribute(string $attribute, string $value = '')
591
    {
592
        $this->applyToAll(function (HasHtmlAttributes $link) use ($attribute, $value) {
593
            $link->setAttribute($attribute, $value);
594
        });
595
596
        return $this;
597
    }
598
599
    /**
600
     * Add a parent class to all items in the menu.
601
     *
602
     * @param string $class
603
     *
604
     * @return $this
605
     */
606
    public function addItemParentClass(string $class)
607
    {
608
        $this->applyToAll(function (HasParentAttributes $item) use ($class) {
609
            $item->addParentClass($class);
610
        });
611
612
        return $this;
613
    }
614
615
    /**
616
     * Add a parent attribute to all items in the menu.
617
     *
618
     * @param string $attribute
619
     * @param string $value
620
     *
621
     * @return $this
622
     */
623
    public function setItemParentAttribute(string $attribute, string $value = '')
624
    {
625
        $this->applyToAll(function (HasParentAttributes $item) use ($attribute, $value) {
626
            $item->setParentAttribute($attribute, $value);
627
        });
628
629
        return $this;
630
    }
631
632
    /**
633
     * Create a empty blueprint of the menu (copies `filters` and `activeClass`).
634
     *
635
     * @return static
636
     */
637
    public function blueprint()
638
    {
639
        $clone = new static();
640
641
        $clone->filters = $this->filters;
642
        $clone->activeClass = $this->activeClass;
643
644
        return $clone;
645
    }
646
647
    /**
648
     * Render the menu.
649
     *
650
     * @return string
651
     */
652
    public function render(): string
653
    {
654
        $contents = HtmlElement::render(
655
            'ul',
656
            $this->htmlAttributes->toArray(),
657
            Arr::map($this->items, function (Item $item) {
658
                return HtmlElement::render(
659
                    $item->isActive() ? "li.{$this->activeClass}" : 'li',
660
                    $item instanceof HasParentAttributes ? $item->getParentAttributes() : [],
661
                    $item->render()
662
                );
663
            })
664
        );
665
666
        $menu = "{$this->prepend}{$contents}{$this->append}";
667
668
        if (!empty($this->wrap)) {
669
            return HtmlElement::render($this->wrap[0], $this->wrap[1], $menu);
670
        }
671
672
        return $menu;
673
    }
674
675
    /**
676
     * The amount of items in the menu.
677
     *
678
     * @return int
679
     */
680
    public function count(): int
681
    {
682
        return count($this->items);
683
    }
684
685
    /**
686
     * @return string
687
     */
688
    public function __toString(): string
689
    {
690
        return $this->render();
691
    }
692
}
693