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 ( f97fa9...d5dc5f )
by Sebastian
03:35
created

Menu::setActiveFromUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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