Passed
Pull Request — master (#57)
by Kyle
08:38
created

MenuBuilder::renderMenu()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 21.5206

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 6
nop 0
dl 0
loc 24
ccs 2
cts 6
cp 0.3333
crap 21.5206
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
namespace KyleMassacre\Menus;
4
5
use Countable;
6
use Illuminate\Contracts\Config\Repository;
7
use Illuminate\Support\Arr;
8
use Illuminate\View\Factory as ViewFactory;
9
use KyleMassacre\Menus\Traits\CanHide;
10
11
class MenuBuilder implements Countable
12
{
13
    use CanHide;
14
    /**
15
     * Menu name.
16
     *
17
     * @var string
18
     */
19
    protected $menu;
20
21
    /**
22
     * Array menu items.
23
     *
24
     * @var array
25
     */
26
    protected $items = [];
27
28
    /**
29
     * Default presenter class.
30
     *
31
     * @var string
32
     */
33
    protected $presenter = Presenters\Bootstrap\NavbarPresenter::class;
34
35
    /**
36
     * Style name for each presenter.
37
     *
38
     * @var array
39
     */
40
    protected $styles = [];
41
42
    /**
43
     * Prefix URL.
44
     *
45
     * @var string|null
46
     */
47
    protected $prefixUrl;
48
49
    /**
50
     * The name of view presenter.
51
     *
52
     * @var string
53
     */
54
    protected $view;
55
56
    /**
57
     * The laravel view factory instance.
58
     *
59
     * @var \Illuminate\View\Factory
60
     */
61
    protected $views;
62
63
    /**
64
     * Determine whether the ordering feature is enabled or not.
65
     *
66
     * @var boolean
67
     */
68
    protected $ordering = false;
69
70
    /**
71
     * Resolved item binding map.
72
     *
73
     * @var array
74
     */
75
    protected $bindings = [];
76
    /**
77
     * @var Repository
78
     */
79
    private $config;
80
81
    /**
82
     * Constructor.
83
     *
84
     * @param string $menu
85 10
     * @param Repository $config
86
     */
87 10
    public function __construct($menu, Repository $config)
88 10
    {
89 10
        $this->menu = $menu;
90
        $this->config = $config;
91
    }
92
93
    /**
94
     * Get menu name.
95
     *
96 1
     * @return string
97
     */
98 1
    public function getName()
99
    {
100
        return $this->menu;
101
    }
102
103
    /**
104
     * Find menu item by given its title.
105
     *
106
     * @param  string        $title
107
     * @param  callable|null $callback
108
     * @return mixed
109
     */
110
    public function whereTitle($title, callable $callback = null)
111
    {
112
        $item = $this->findBy('title', $title);
113
114
        if (is_callable($callback)) {
115
            return call_user_func($callback, $item);
0 ignored issues
show
Bug introduced by
It seems like $callback can also be of type null; however, parameter $callback of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

115
            return call_user_func(/** @scrutinizer ignore-type */ $callback, $item);
Loading history...
116
        }
117
118
        return $item;
119
    }
120
121
    /**
122
     * Find menu item by given key and value.
123
     *
124
     * @param  string $key
125
     * @param  string $value
126
     * @return \KyleMassacre\Menus\MenuItem
127
     */
128
    public function findBy($key, $value)
129
    {
130
        return collect($this->items)->filter(function ($item) use ($key, $value) {
0 ignored issues
show
Bug introduced by
$this->items of type array is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

130
        return collect(/** @scrutinizer ignore-type */ $this->items)->filter(function ($item) use ($key, $value) {
Loading history...
131
            return $item->{$key} == $value;
132
        })->first();
133
    }
134
135
    /**
136
     * Set view factory instance.
137
     *
138
     * @param ViewFactory $views
139
     *
140 9
     * @return $this
141
     */
142 9
    public function setViewFactory(ViewFactory $views)
143
    {
144 9
        $this->views = $views;
145
146
        return $this;
147
    }
148
149
    /**
150
     * Set view.
151
     *
152
     * @param string $view
153
     *
154
     * @return $this
155
     */
156
    public function setView($view)
157
    {
158
        $this->view = $view;
159
160
        return $this;
161
    }
162
163
    /**
164
     * Set Prefix URL.
165
     *
166
     * @param string $prefixUrl
167
     *
168
     * @return $this
169
     */
170
    public function setPrefixUrl($prefixUrl)
171
    {
172
        $this->prefixUrl = $prefixUrl;
173
174
        return $this;
175
    }
176
177
    /**
178
     * Set styles.
179
     *
180
     * @param array $styles
181
     */
182
    public function setStyles(array $styles)
183
    {
184
        $this->styles = $styles;
185
    }
186
187
    /**
188
     * Set new presenter class.
189
     *
190
     * @param string $presenter
191
     */
192
    public function setPresenter($presenter)
193
    {
194
        $this->presenter = $presenter;
195
    }
196
197
    /**
198
     * Get presenter instance.
199
     *
200 3
     * @return \KyleMassacre\Menus\Presenters\PresenterInterface
201
     */
202 3
    public function getPresenter()
203
    {
204
        return new $this->presenter();
205
    }
206
207
    /**
208
     * Set new presenter class by given style name.
209
     *
210
     * @param string $name
211
     *
212
     * @return self
213
     */
214
    public function style($name)
215
    {
216
        if ($this->hasStyle($name)) {
217
            $this->setPresenter($this->getStyle($name));
218
        }
219
220
        return $this;
221
    }
222
223
    /**
224
     * Determine if the given name in the presenter style.
225
     *
226
     * @param $name
227
     *
228 3
     * @return bool
229
     */
230 3
    public function hasStyle($name)
231
    {
232
        return array_key_exists($name, $this->getStyles());
233
    }
234
235
    /**
236
     * Get style aliases.
237
     *
238 3
     * @return mixed
239
     */
240 3
    public function getStyles()
241
    {
242
        return $this->styles ?: $this->config->get('menus.styles');
243
    }
244
245
    /**
246
     * Get the presenter class name by given alias name.
247
     *
248
     * @param $name
249
     *
250
     * @return mixed
251
     */
252
    public function getStyle($name)
253
    {
254
        $style = $this->getStyles();
255
256
        return $style[$name];
257
    }
258
259
    /**
260
     * Set new presenter class from given alias name.
261
     *
262
     * @param $name
263
     */
264
    public function setPresenterFromStyle($name)
265
    {
266
        $this->setPresenter($this->getStyle($name));
267
    }
268
269
    /**
270
     * Set the resolved item bindings
271
     *
272
     * @param array $bindings
273 3
     * @return $this
274
     */
275 3
    public function setBindings(array $bindings)
276
    {
277 3
        $this->bindings = $bindings;
278
279
        return $this;
280
    }
281
282
    /**
283
     * Resolves a key from the bindings array.
284
     *
285
     * @param  string|array $key
286
     * @return mixed
287
     */
288
    public function resolve($key)
289
    {
290
        if (is_array($key)) {
291
            foreach ($key as $k => $v) {
292
                $key[$k] = $this->resolve($v);
293
            }
294
        } elseif (is_string($key)) {
0 ignored issues
show
introduced by
The condition is_string($key) is always true.
Loading history...
295
            $matches = array();
296
            preg_match_all('/{[\s]*?([^\s]+)[\s]*?}/i', $key, $matches, PREG_SET_ORDER);
297
            foreach ($matches as $match) {
298
                if (array_key_exists($match[1], $this->bindings)) {
299
                    $key = preg_replace('/' . $match[0] . '/', $this->bindings[$match[1]], $key, 1);
300
                }
301
            }
302
        }
303
304
        return $key;
305
    }
306
307
    /**
308
     * Resolves an array of menu items properties.
309
     *
310
     * @param  array  &$items
311 3
     * @return void
312
     */
313
    protected function resolveItems(array &$items)
314
    {
315 3
        $resolver = function ($property) {
316
            return $this->resolve($property) ?: $property;
317 3
        };
318 3
319
        $totalItems = count($items);
320
        for ($i = 0; $i < $totalItems; $i++) {
321 3
            $items[$i]->fill(array_map($resolver, $items[$i]->getProperties()));
322
        }
323
    }
324
325
    /**
326
     * Add new child menu.
327
     *
328
     * @param array $attributes
329
     *
330
     * @return \KyleMassacre\Menus\MenuItem
331
     */
332
    public function add(array $attributes = array())
333
    {
334
        $item = MenuItem::make($attributes);
335
336
        $this->items[] = $item;
337
338
        return $item;
339
    }
340
341
    /**
342
     * Create new menu with dropdown.
343
     *
344
     * @param $title
345
     * @param callable $callback
346
     * @param array    $attributes
347
     *
348
     * @return $this
349
     */
350
    public function dropdown($title, \Closure $callback, $order = null, array $attributes = array())
351
    {
352
        $properties = compact('title', 'order', 'attributes');
353
354
        if (func_num_args() == 3) {
355
            $arguments = func_get_args();
356
357
            $title = Arr::get($arguments, 0);
358
            $attributes = Arr::get($arguments, 2);
359
360
            $properties = compact('title', 'attributes');
361
        }
362
363
        $item = MenuItem::make($properties);
364
365
        call_user_func($callback, $item);
366
367
        $this->items[] = $item;
368
369
        return $item;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $item returns the type KyleMassacre\Menus\MenuItem which is incompatible with the documented return type KyleMassacre\Menus\MenuBuilder.
Loading history...
370
    }
371
372
    /**
373
     * Register new menu item using registered route.
374
     *
375
     * @param $route
376
     * @param $title
377
     * @param array $parameters
378
     * @param array $attributes
379
     *
380
     * @return static
381
     */
382
    public function route($route, $title, $parameters = array(), $order = null, $attributes = array())
383
    {
384
        if (func_num_args() == 4) {
385
            $arguments = func_get_args();
386
387
            return $this->add([
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->add(array(...r::get($arguments, 3))) returns the type KyleMassacre\Menus\MenuItem which is incompatible with the documented return type KyleMassacre\Menus\MenuBuilder.
Loading history...
388
                'route' => [Arr::get($arguments, 0), Arr::get($arguments, 2)],
389
                'title' => Arr::get($arguments, 1),
390
                'attributes' => Arr::get($arguments, 3),
391
            ]);
392
        }
393
394
        $route = array($route, $parameters);
395
396
        $item = MenuItem::make(
397
            compact('route', 'title', 'parameters', 'attributes', 'order')
398
        );
399
400
        $this->items[] = $item;
401
402
        return $item;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $item returns the type KyleMassacre\Menus\MenuItem which is incompatible with the documented return type KyleMassacre\Menus\MenuBuilder.
Loading history...
403
    }
404
405
    /**
406
     * Format URL.
407
     *
408
     * @param string $url
409
     *
410 2
     * @return string
411
     */
412 2
    protected function formatUrl($url)
413
    {
414 2
        $uri = !is_null($this->prefixUrl) ? $this->prefixUrl . $url : $url;
415
416
        return $uri == '/' ? '/' : ltrim(rtrim($uri, '/'), '/');
417
    }
418
419
    /**
420
     * Register new menu item using url.
421
     *
422
     * @param $url
423
     * @param $title
424
     * @param array $attributes
425
     *
426 2
     * @return static
427
     */
428 2
    public function url($url, $title, $order = 0, $attributes = array())
429
    {
430
        if (func_num_args() == 3) {
431
            $arguments = func_get_args();
432
433
            return $this->add([
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->add(array(...r::get($arguments, 2))) returns the type KyleMassacre\Menus\MenuItem which is incompatible with the documented return type KyleMassacre\Menus\MenuBuilder.
Loading history...
434
                'url' => $this->formatUrl(Arr::get($arguments, 0)),
435
                'title' => Arr::get($arguments, 1),
436
                'attributes' => Arr::get($arguments, 2),
437
            ]);
438 2
        }
439
440 2
        $url = $this->formatUrl($url);
441
442 2
        $item = MenuItem::make(compact('url', 'title', 'order', 'attributes'));
443
444 2
        $this->items[] = $item;
445
446
        return $item;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $item returns the type KyleMassacre\Menus\MenuItem which is incompatible with the documented return type KyleMassacre\Menus\MenuBuilder.
Loading history...
447
    }
448
449
    /**
450
     * Add new divider item.
451
     *
452
     * @param int $order
453
     * @return \KyleMassacre\Menus\MenuItem
454
     */
455
    public function addDivider($order = null)
456
    {
457
        $this->items[] = new MenuItem(array('name' => 'divider', 'order' => $order));
458
459
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type KyleMassacre\Menus\MenuBuilder which is incompatible with the documented return type KyleMassacre\Menus\MenuItem.
Loading history...
460
    }
461
462
    public function addBadge(string $type, $text)
463
    {
464
        $properties = array(
465
            'type' => $type,
466
            'text' => $text,
467
            'name' => 'badge',
468
        );
469
470
        return $this->add($properties);
471
    }
472
473
    /**
474
     * Add new header item.
475
     *
476
     * @return \KyleMassacre\Menus\MenuItem
477
     */
478
    public function addHeader($title, $order = null)
479
    {
480
        $this->items[] = new MenuItem(array(
481
            'name' => 'header',
482
            'title' => $title,
483
            'order' => $order,
484
        ));
485
486
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type KyleMassacre\Menus\MenuBuilder which is incompatible with the documented return type KyleMassacre\Menus\MenuItem.
Loading history...
487
    }
488
489
    /**
490
     * Alias for "addHeader" method.
491
     *
492
     * @param string $title
493
     *
494
     * @return $this
495
     */
496
    public function header($title)
497
    {
498
        return $this->addHeader($title);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->addHeader($title) returns the type KyleMassacre\Menus\MenuItem which is incompatible with the documented return type KyleMassacre\Menus\MenuBuilder.
Loading history...
499
    }
500
501
    /**
502
     * Alias for "addDivider" method.
503 1
     *
504
     * @return $this
505 1
     */
506
    public function divider()
507
    {
508
        return $this->addDivider();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->addDivider() returns the type KyleMassacre\Menus\MenuItem which is incompatible with the documented return type KyleMassacre\Menus\MenuBuilder.
Loading history...
509
    }
510
511
    /**
512
     * Get items count.
513
     *
514
     * @return int
515
     */
516
    public function count()
517
    {
518
        return count($this->items);
519
    }
520
521
    /**
522
     * Empty the current menu items.
523
     */
524
    public function destroy()
525 3
    {
526
        $this->items = array();
527 3
528
        return $this;
529 3
    }
530
531
    /**
532
     * Render the menu to HTML tag.
533 3
     *
534
     * @param string $presenter
535
     *
536
     * @return string
537 3
     */
538
    public function render($presenter = null)
539
    {
540
        $this->resolveItems($this->items);
541 3
542
        if (!is_null($this->view)) {
0 ignored issues
show
introduced by
The condition is_null($this->view) is always false.
Loading history...
543
            return $this->renderView($presenter);
544
        }
545
546
        if ($this->hasStyle($presenter)) {
547
            $this->setPresenterFromStyle($presenter);
548
        }
549
550
        if (!is_null($presenter) && !$this->hasStyle($presenter)) {
551
            $this->setPresenter($presenter);
552
        }
553
554
        return $this->renderMenu();
555
    }
556
557
    /**
558
     * Render menu via view presenter.
559
     *
560
     * @return \Illuminate\View\View
561
     */
562
    public function renderView($presenter = null)
563
    {
564
        return $this->views->make($presenter ?: $this->view, [
565
            'items' => $this->getOrderedItems(),
566
        ]);
567
    }
568
569
    /**
570
     * Get original items.
571
     *
572
     * @return array
573
     */
574
    public function getItems()
575
    {
576
        return $this->items;
577
    }
578
579
    /**
580
     * Get menu items as laravel collection instance.
581
     *
582
     * @return \Illuminate\Support\Collection
583
     */
584
    public function toCollection()
585
    {
586
        return collect($this->items);
0 ignored issues
show
Bug introduced by
$this->items of type array is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

586
        return collect(/** @scrutinizer ignore-type */ $this->items);
Loading history...
587
    }
588
589
    /**
590
     * Get menu items as array.
591
     *
592
     * @return array
593
     */
594
    public function toArray()
595
    {
596
        return $this->toCollection()->toArray();
597
    }
598
599
    /**
600
     * Enable menu ordering.
601
     *
602
     * @return self
603
     */
604
    public function enableOrdering()
605
    {
606
        $this->ordering = true;
607
608
        return $this;
609
    }
610
611
    /**
612
     * Disable menu ordering.
613
     *
614
     * @return self
615 3
     */
616
    public function disableOrdering()
617 3
    {
618
        $this->ordering = false;
619
620
        return $this;
621
    }
622
623 3
    /**
624
     * Get menu items and order it by 'order' key.
625
     *
626
     * @return array
627
     */
628
    public function getOrderedItems()
629
    {
630
        if (config('menus.ordering') || $this->ordering) {
631 3
            return $this->toCollection()->sortBy(function ($item) {
632
                return $item->order;
633 3
            })->all();
634 3
        }
635
636 3
        return $this->items;
637
    }
638
639
    /**
640
     * Render the menu.
641
     *
642
     * @return string
643
     */
644
    protected function renderMenu()
645
    {
646
        $presenter = $this->getPresenter();
647
        $menu = $presenter->getOpenTagWrapper();
648
649
        foreach ($this->getOrderedItems() as $item) {
650
            if ($item->hidden() || $this->hidden()) {
651
                continue;
652 3
            }
653
654 3
            if ($item->hasSubMenu()) {
655
                $menu .= $presenter->getMenuWithDropDownWrapper($item);
656
            } elseif ($item->isHeader()) {
657
                $menu .= $presenter->getHeaderWrapper($item);
658
            } elseif ($item->isDivider()) {
659
                $menu .= $presenter->getDividerWrapper();
660
            } else {
661
                $menu .= $presenter->getMenuWithoutDropdownWrapper($item);
662
            }
663
        }
664
665
        $menu .= $presenter->getCloseTagWrapper();
666
667
        return $menu;
668
    }
669
}
670