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 ( 28f3b8...14c6af )
by Freek
03:03
created

Menu::if()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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