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
Pull Request — master (#64)
by Mark
07:39
created

Menu::setActiveClassOnLink()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
namespace Spatie\Menu;
4
5
use Countable;
6
use Spatie\Menu\Html\Tag;
7
use Spatie\Menu\Html\Attributes;
8
use Spatie\Menu\Helpers\Reflection;
9
use Spatie\Menu\Traits\Conditions as ConditionsTrait;
10
use Spatie\Menu\Traits\HasTextAttributes as HasAttributesTrait;
11
use Spatie\Menu\Traits\HasHtmlAttributes as HasHtmlAttributesTrait;
12
use Spatie\Menu\Traits\HasParentAttributes as HasParentAttributesTrait;
13
14
class Menu implements Item, Countable, HasHtmlAttributes, HasParentAttributes
15
{
16
    use HasHtmlAttributesTrait, HasParentAttributesTrait, ConditionsTrait, HasAttributesTrait;
17
18
    /** @var array */
19
    protected $items = [];
20
21
    /** @var array */
22
    protected $filters = [];
23
24
    /** @var string */
25
    protected $prepend, $append = '';
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
26
27
    /** @var array */
28
    protected $wrap = [];
29
30
    /** @var string */
31
    protected $activeClass = 'active';
32
33
    /** @var string */
34
    protected $tagName = 'ul';
35
36
    /** @var bool */
37
    protected $wrapLinksInList = true;
38
39
    /** @var bool */
40
    protected $activeClassOnLink = false;
41
42
    /** @var \Spatie\Menu\Html\Attributes */
43
    protected $htmlAttributes, $parentAttributes;
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
44
45
    protected function __construct(Item ...$items)
46
    {
47
        $this->items = $items;
48
49
        $this->htmlAttributes = new Attributes();
50
        $this->parentAttributes = new Attributes();
51
    }
52
53
    /**
54
     * Create a new menu, optionally prefilled with items.
55
     *
56
     * @param array $items
57
     *
58
     * @return static
59
     */
60
    public static function new($items = [])
61
    {
62
        return new static(...array_values($items));
63
    }
64
65
    /**
66
     * Build a new menu from an array. The callback receives a menu instance as
67
     * the accumulator, the array item as the second parameter, and the item's
68
     * key as the third.
69
     *
70
     * @param array|\Iterator $items
71
     * @param callable $callback
72
     * @param \Spatie\Menu\Menu|null $initial
73
     *
74
     * @return static
75
     */
76
    public static function build($items, callable $callback, self $initial = null)
77
    {
78
        return ($initial ?: static::new())->fill($items, $callback);
79
    }
80
81
    /**
82
     * Fill a menu from an array. The callback receives a menu instance as
83
     * the accumulator, the array item as the second parameter, and the item's
84
     * key as the third.
85
     *
86
     * @param array|\Iterator $items
87
     * @param callable $callback
88
     *
89
     * @return static
90
     */
91
    public function fill($items, callable $callback)
92
    {
93
        $menu = $this;
94
95
        foreach ($items as $key => $item) {
96
            $menu = $callback($menu, $item, $key) ?: $menu;
97
        }
98
99
        return $menu;
100
    }
101
102
    /**
103
     * Add an item to the menu. This also applies all registered filters to the
104
     * item.
105
     *
106
     * @param \Spatie\Menu\Item $item
107
     *
108
     * @return $this
109
     */
110
    public function add(Item $item)
111
    {
112
        foreach ($this->filters as $filter) {
113
            $this->applyFilter($filter, $item);
114
        }
115
116
        $this->items[] = $item;
117
118
        return $this;
119
    }
120
121
    /**
122
     * Add an item to the menu if a (non-strict) condition is met.
123
     *
124
     * @param bool              $condition
125
     * @param \Spatie\Menu\Item $item
126
     *
127
     * @return $this
128
     */
129
    public function addIf($condition, Item $item)
130
    {
131
        if ($this->resolveCondition($condition)) {
132
            $this->add($item);
133
        }
134
135
        return $this;
136
    }
137
138
    /**
139
     * Shortcut function to add a plain link to the menu.
140
     *
141
     * @param string $url
142
     * @param string $text
143
     *
144
     * @return $this
145
     */
146
    public function link(string $url, string $text)
147
    {
148
        return $this->add(Link::to($url, $text));
149
    }
150
151
    /**
152
     * Shortcut function to add an empty item to the menu.
153
     *
154
     * @return $this
155
     */
156
    public function empty()
157
    {
158
        return $this->add(Html::empty());
159
    }
160
161
    /**
162
     * Add a link to the menu if a (non-strict) condition is met.
163
     *
164
     * @param bool   $condition
165
     * @param string $url
166
     * @param string $text
167
     *
168
     * @return $this
169
     */
170
    public function linkIf($condition, string $url, string $text)
171
    {
172
        if ($this->resolveCondition($condition)) {
173
            $this->link($url, $text);
174
        }
175
176
        return $this;
177
    }
178
179
    /**
180
     * Shortcut function to add raw html to the menu.
181
     *
182
     * @param string $html
183
     * @param array  $parentAttributes
184
     *
185
     * @return $this
186
     */
187
    public function html(string $html, array $parentAttributes = [])
188
    {
189
        return $this->add(Html::raw($html)->setParentAttributes($parentAttributes));
190
    }
191
192
    /**
193
     * Add a chunk of html if a (non-strict) condition is met.
194
     *
195
     * @param bool   $condition
196
     * @param string $html
197
     * @param array  $parentAttributes
198
     *
199
     * @return $this
200
     */
201
    public function htmlIf($condition, string $html, array $parentAttributes = [])
202
    {
203
        if ($this->resolveCondition($condition)) {
204
            $this->html($html, $parentAttributes);
205
        }
206
207
        return $this;
208
    }
209
210
    /**
211
     * @param callable|\Spatie\Menu\Menu|\Spatie\Menu\Item $header
212
     * @param callable|\Spatie\Menu\Menu|null $menu
213
     *
214
     * @return $this
215
     */
216
    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...
217
    {
218
        list($header, $menu) = $this->parseSubmenuArgs(func_get_args());
219
220
        $menu = $this->createSubmenuMenu($menu);
221
        $header = $this->createSubmenuHeader($header);
222
223
        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...
224
    }
225
226
    /**
227
     * @param bool $condition
228
     * @param callable|\Spatie\Menu\Menu|\Spatie\Menu\Item $header
229
     * @param callable|\Spatie\Menu\Menu|null $menu
230
     *
231
     * @return $this
232
     */
233
    public function submenuIf($condition, $header, $menu = null)
234
    {
235
        if ($condition) {
236
            $this->submenu($header, $menu);
237
        }
238
239
        return $this;
240
    }
241
242
    protected function parseSubmenuArgs($args): array
243
    {
244
        if (count($args) === 1) {
245
            return ['', $args[0]];
246
        }
247
248
        return [$args[0], $args[1]];
249
    }
250
251
    /**
252
     * @param \Spatie\Menu\Menu|callable $menu
253
     *
254
     * @return \Spatie\Menu\Menu
255
     */
256
    protected function createSubmenuMenu($menu): self
257
    {
258
        if (is_callable($menu)) {
259
            $transformer = $menu;
260
            $menu = $this->blueprint();
261
            $transformer($menu);
262
        }
263
264
        return $menu;
265
    }
266
267
    /**
268
     * @param \Spatie\Menu\Item|string $header
269
     *
270
     * @return string
271
     */
272
    protected function createSubmenuHeader($header): string
273
    {
274
        if ($header instanceof Item) {
275
            $header = $header->render();
276
        }
277
278
        return $header;
279
    }
280
281
    /**
282
     * Iterate over all the items and apply a callback. If you typehint the
283
     * item parameter in the callable, it wil only be applied to items of that
284
     * type.
285
     *
286
     * @param callable $callable
287
     *
288
     * @return $this
289
     */
290
    public function each(callable $callable)
291
    {
292
        $type = Reflection::firstParameterType($callable);
293
294
        foreach ($this->items as $item) {
295
            if (! Reflection::itemMatchesType($item, $type)) {
296
                continue;
297
            }
298
299
            $callable($item);
300
        }
301
302
        return $this;
303
    }
304
305
    /**
306
     * Register a filter to the menu. When an item is added, all filters will be
307
     * applied to the item. If you typehint the item parameter in the callable, it
308
     * will only be applied to items of that type.
309
     *
310
     * @param callable $callable
311
     *
312
     * @return $this
313
     */
314
    public function registerFilter(callable $callable)
315
    {
316
        $this->filters[] = $callable;
317
318
        return $this;
319
    }
320
321
    /**
322
     * Apply a filter to an item. Returns the result of the filter.
323
     *
324
     * @param callable          $filter
325
     * @param \Spatie\Menu\Item $item
326
     */
327
    protected function applyFilter(callable $filter, Item $item)
328
    {
329
        $type = Reflection::firstParameterType($filter);
330
331
        if (! Reflection::itemMatchesType($item, $type)) {
332
            return;
333
        }
334
335
        $filter($item);
336
    }
337
338
    /**
339
     * Apply a callable to all existing items, and register it as a filter so it
340
     * will get applied to all new items too. If you typehint the item parameter
341
     * in the callable, it wil only be applied to items of that type.
342
     *
343
     * @param callable $callable
344
     *
345
     * @return $this
346
     */
347
    public function applyToAll(callable $callable)
348
    {
349
        $this->each($callable);
350
        $this->registerFilter($callable);
351
352
        return $this;
353
    }
354
355
    /**
356
     * Wrap the menu in an html element.
357
     *
358
     * @param string $element
359
     * @param array $attributes
360
     *
361
     * @return $this
362
     */
363
    public function wrap(string $element, $attributes = [])
364
    {
365
        $this->wrap = [$element, $attributes];
366
367
        return $this;
368
    }
369
370
    /**
371
     * Determine whether the menu is active.
372
     *
373
     * @return bool
374
     */
375
    public function isActive(): bool
376
    {
377
        foreach ($this->items as $item) {
378
            if ($item->isActive()) {
379
                return true;
380
            }
381
        }
382
383
        return false;
384
    }
385
386
    /**
387
     * Set multiple items in the menu as active based on a callable that filters
388
     * through items. If you typehint the item parameter in the callable, it will
389
     * only be applied to items of that type.
390
     *
391
     * @param callable|string $urlOrCallable
392
     * @param string          $root
393
     *
394
     * @return $this
395
     */
396
    public function setActive($urlOrCallable, string $root = '/')
397
    {
398
        if (is_string($urlOrCallable)) {
399
            return $this->setActiveFromUrl($urlOrCallable, $root);
400
        }
401
402
        if (is_callable($urlOrCallable)) {
403
            return $this->setActiveFromCallable($urlOrCallable);
404
        }
405
406
        throw new \InvalidArgumentException('`setActive` requires a pattern or a callable');
407
    }
408
409
    /**
410
     * Set all relevant children active based on the current request's URL.
411
     *
412
     * /, /about, /contact => request to /about will set the about link active.
413
     *
414
     * /en, /en/about, /en/contact => request to /en won't set /en active if the
415
     *                                request root is set to /en.
416
     *
417
     * @param string $url  The current request url.
418
     * @param string $root If the link's URL is an exact match with the request
419
     *                     root, the link won't be set active. This behavior is
420
     *                     to avoid having home links active on every request.
421
     *
422
     * @return $this
423
     */
424
    public function setActiveFromUrl(string $url, string $root = '/')
425
    {
426
        $this->applyToAll(function (Menu $menu) use ($url, $root) {
427
            $menu->setActiveFromUrl($url, $root);
428
        });
429
430
        $this->applyToAll(function (Activatable $item) use ($url, $root) {
431
            $item->determineActiveForUrl($url, $root);
432
        });
433
434
        return $this;
435
    }
436
437
    /**
438
     * @param callable $callable
439
     *
440
     * @return $this
441
     */
442
    public function setActiveFromCallable(callable $callable)
443
    {
444
        $this->applyToAll(function (Menu $menu) use ($callable) {
445
            $menu->setActiveFromCallable($callable);
446
        });
447
448
        $type = Reflection::firstParameterType($callable);
449
450
        $this->applyToAll(function (Activatable $item) use ($callable, $type) {
451
            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...
452
                return;
453
            }
454
455
            if ($callable($item)) {
456
                $item->setActive();
457
            }
458
        });
459
460
        return $this;
461
    }
462
463
    /**
464
     * Set the class name that will be used on active items for this menu.
465
     *
466
     * @param string $class
467
     *
468
     * @return $this
469
     */
470
    public function setActiveClass(string $class)
471
    {
472
        $this->activeClass = $class;
473
474
        return $this;
475
    }
476
477
    /**
478
     * Add a class to all items in the menu.
479
     *
480
     * @param string $class
481
     *
482
     * @return $this
483
     */
484
    public function addItemClass(string $class)
485
    {
486
        $this->applyToAll(function (HasHtmlAttributes $link) use ($class) {
487
            $link->addClass($class);
488
        });
489
490
        return $this;
491
    }
492
493
    /**
494
     * Set an attribute on all items in the menu.
495
     *
496
     * @param string $attribute
497
     * @param string $value
498
     *
499
     * @return $this
500
     */
501
    public function setItemAttribute(string $attribute, string $value = '')
502
    {
503
        $this->applyToAll(function (HasHtmlAttributes $link) use ($attribute, $value) {
504
            $link->setAttribute($attribute, $value);
505
        });
506
507
        return $this;
508
    }
509
510
    /**
511
     * Add a parent class to all items in the menu.
512
     *
513
     * @param string $class
514
     *
515
     * @return $this
516
     */
517
    public function addItemParentClass(string $class)
518
    {
519
        $this->applyToAll(function (HasParentAttributes $item) use ($class) {
520
            $item->addParentClass($class);
521
        });
522
523
        return $this;
524
    }
525
526
    /**
527
     * Add a parent attribute to all items in the menu.
528
     *
529
     * @param string $attribute
530
     * @param string $value
531
     *
532
     * @return $this
533
     */
534
    public function setItemParentAttribute(string $attribute, string $value = '')
535
    {
536
        $this->applyToAll(function (HasParentAttributes $item) use ($attribute, $value) {
537
            $item->setParentAttribute($attribute, $value);
538
        });
539
540
        return $this;
541
    }
542
543
    /**
544
     * Set tag for items wrapper
545
     *
546
     * @param string $tagName
547
     * @return $this
548
     */
549
    public function setTagName(string $tagName)
550
    {
551
        $this->tagName = $tagName;
552
553
        return $this;
554
    }
555
556
    /**
557
     * Set whether links should be wrapped in a list item
558
     *
559
     * @param $wrapLinksInList
560
     * @return $this
561
     */
562
    public function setWrapLinksInList(bool $wrapLinksInList)
563
    {
564
        $this->wrapLinksInList = $wrapLinksInList;
565
566
        return $this;
567
    }
568
569
    /**
570
     * Set whether active class should (also) be on link
571
     *
572
     * @param $activeClassOnLink
573
     * @return $this
574
     */
575
    public function setActiveClassOnLink(bool $activeClassOnLink)
576
    {
577
        $this->activeClassOnLink = $activeClassOnLink;
578
579
        return $this;
580
    }
581
582
    /**
583
     * @param bool $condition
584
     * @param callable $callable
585
     *
586
     * @return $this
587
     */
588
    public function if(bool $condition, callable $callable)
0 ignored issues
show
Coding Style introduced by
Possible parse error: non-abstract method defined as abstract
Loading history...
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
589
    {
590
        return $condition ? $callable($this) : $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
        $tag = new Tag($this->tagName, $this->htmlAttributes);
616
617
        $contents = array_map([$this, 'renderItem'], $this->items);
618
619
        $menu = $this->prepend.$tag->withContents($contents).$this->append;
620
621
        if (! empty($this->wrap)) {
622
            return Tag::make($this->wrap[0], new Attributes($this->wrap[1]))->withContents($menu);
623
        }
624
625
        return $menu;
626
    }
627
628
    protected function renderItem(Item $item): string
629
    {
630
        $attributes = new Attributes();
631
632
        if ($item->isActive()) {
633
            $attributes->addClass($this->activeClass);
634
635
            if($this->activeClassOnLink && $item instanceof HasHtmlAttributes) {
636
                $item->addClass($this->activeClass);
637
            }
638
        }
639
640
        if ($item instanceof HasParentAttributes) {
641
            $attributes->setAttributes($item->parentAttributes());
642
        }
643
644
        if(! $this->wrapLinksInList) {
645
            return $item->render();
646
        }
647
648
        return Tag::make('li', $attributes)->withContents($item->render());
649
    }
650
651
    /**
652
     * The amount of items in the menu.
653
     *
654
     * @return int
655
     */
656
    public function count(): int
657
    {
658
        return count($this->items);
659
    }
660
661
    /**
662
     * @return string
663
     */
664
    public function __toString(): string
665
    {
666
        return $this->render();
667
    }
668
}
669