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 (#44)
by
unknown
02:47
created

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