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 ( b73c4c...cb9554 )
by Sebastian
02:58
created

Menu::addItemParentClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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