Completed
Push — master ( 12a2e2...71a5fb )
by Nicolas
12s
created

MenuBuilder::dropdown()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 11

Duplication

Lines 21
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 21
loc 21
ccs 0
cts 12
cp 0
rs 9.3142
c 0
b 0
f 0
cc 2
eloc 11
nc 2
nop 4
crap 6
1
<?php
2
3
namespace Nwidart\Menus;
4
5
use Countable;
6
use Illuminate\Config\Repository;
7
use Illuminate\View\Factory as ViewFactory;
8
9
class MenuBuilder implements Countable
10
{
11
    /**
12
     * Menu name.
13
     *
14
     * @var string
15
     */
16
    protected $menu;
17
18
    /**
19
     * Array menu items.
20
     *
21
     * @var array
22
     */
23
    protected $items = [];
24
25
    /**
26
     * Default presenter class.
27
     *
28
     * @var string
29
     */
30
    protected $presenter = Presenters\Bootstrap\NavbarPresenter::class;
31
32
    /**
33
     * Style name for each presenter.
34
     *
35
     * @var array
36
     */
37
    protected $styles = [];
38
39
    /**
40
     * Prefix URL.
41
     *
42
     * @var string|null
43
     */
44
    protected $prefixUrl;
45
46
    /**
47
     * The name of view presenter.
48
     *
49
     * @var string
50
     */
51
    protected $view;
52
53
    /**
54
     * The laravel view factory instance.
55
     *
56
     * @var \Illuminate\View\Factory
57
     */
58
    protected $views;
59
60
    /**
61
     * Determine whether the ordering feature is enabled or not.
62
     *
63
     * @var boolean
64
     */
65
    protected $ordering = false;
66
67
    /**
68
     * Resolved item binding map.
69
     *
70
     * @var array
71
     */
72
    protected $bindings = [];
73
    /**
74
     * @var Repository
75
     */
76
    private $config;
77
78
    /**
79
     * Constructor.
80
     *
81
     * @param string $menu
82
     * @param Repository $config
83
     */
84 10
    public function __construct($menu, Repository $config)
85
    {
86 10
        $this->menu = $menu;
87 10
        $this->config = $config;
88 10
    }
89
90
    /**
91
     * Get menu name.
92
     *
93
     * @return string
94
     */
95 1
    public function getName()
96
    {
97 1
        return $this->menu;
98
    }
99
100
    /**
101
     * Find menu item by given its title.
102
     *
103
     * @param  string        $title
104
     * @param  callable|null $callback
105
     * @return mixed
106
     */
107
    public function whereTitle($title, callable $callback = null)
108
    {
109
        $item = $this->findBy('title', $title);
110
111
        if (is_callable($callback)) {
112
            return call_user_func($callback, $item);
113
        }
114
115
        return $item;
116
    }
117
118
    /**
119
     * Find menu item by given key and value.
120
     *
121
     * @param  string $key
122
     * @param  string $value
123
     * @return \Nwidart\Menus\MenuItem
124
     */
125
    public function findBy($key, $value)
126
    {
127
        return collect($this->items)->filter(function ($item) use ($key, $value) {
128
            return $item->{$key} == $value;
129
        })->first();
130
    }
131
132
    /**
133
     * Set view factory instance.
134
     *
135
     * @param ViewFactory $views
136
     *
137
     * @return $this
138
     */
139 9
    public function setViewFactory(ViewFactory $views)
140
    {
141 9
        $this->views = $views;
142
143 9
        return $this;
144
    }
145
146
    /**
147
     * Set view.
148
     *
149
     * @param string $view
150
     *
151
     * @return $this
152
     */
153
    public function setView($view)
154
    {
155
        $this->view = $view;
156
157
        return $this;
158
    }
159
160
    /**
161
     * Set Prefix URL.
162
     *
163
     * @param string $prefixUrl
164
     *
165
     * @return $this
166
     */
167
    public function setPrefixUrl($prefixUrl)
168
    {
169
        $this->prefixUrl = $prefixUrl;
170
171
        return $this;
172
    }
173
174
    /**
175
     * Set styles.
176
     *
177
     * @param array $styles
178
     */
179
    public function setStyles(array $styles)
180
    {
181
        $this->styles = $styles;
182
    }
183
184
    /**
185
     * Set new presenter class.
186
     *
187
     * @param string $presenter
188
     */
189
    public function setPresenter($presenter)
190
    {
191
        $this->presenter = $presenter;
192
    }
193
194
    /**
195
     * Get presenter instance.
196
     *
197
     * @return \Nwidart\Menus\Presenters\PresenterInterface
198
     */
199 3
    public function getPresenter()
200
    {
201 3
        return new $this->presenter();
202
    }
203
204
    /**
205
     * Set new presenter class by given style name.
206
     *
207
     * @param string $name
208
     *
209
     * @return self
210
     */
211
    public function style($name)
212
    {
213
        if ($this->hasStyle($name)) {
214
            $this->setPresenter($this->getStyle($name));
215
        }
216
217
        return $this;
218
    }
219
220
    /**
221
     * Determine if the given name in the presenter style.
222
     *
223
     * @param $name
224
     *
225
     * @return bool
226
     */
227 3
    public function hasStyle($name)
228
    {
229 3
        return array_key_exists($name, $this->getStyles());
230
    }
231
232
    /**
233
     * Get style aliases.
234
     *
235
     * @return mixed
236
     */
237 3
    public function getStyles()
238
    {
239 3
        return $this->styles ?: $this->config->get('menus.styles');
240
    }
241
242
    /**
243
     * Get the presenter class name by given alias name.
244
     *
245
     * @param $name
246
     *
247
     * @return mixed
248
     */
249
    public function getStyle($name)
250
    {
251
        $style = $this->getStyles();
252
253
        return $style[$name];
254
    }
255
256
    /**
257
     * Set new presenter class from given alias name.
258
     *
259
     * @param $name
260
     */
261
    public function setPresenterFromStyle($name)
262
    {
263
        $this->setPresenter($this->getStyle($name));
264
    }
265
266
    /**
267
     * Set the resolved item bindings
268
     *
269
     * @param array $bindings
270
     * @return $this
271
     */
272 3
    public function setBindings(array $bindings)
273
    {
274 3
        $this->bindings = $bindings;
275
276 3
        return $this;
277
    }
278
279
    /**
280
     * Resolves a key from the bindings array.
281
     *
282
     * @param  string|array $key
283
     * @return mixed
284
     */
285
    public function resolve($key)
286
    {
287
        if (is_array($key)) {
288
            foreach ($key as $k => $v) {
289
                $key[$k] = $this->resolve($v);
290
            }
291
        } elseif (is_string($key)) {
292
            $matches = array();
293
            preg_match_all('/{[\s]*?([^\s]+)[\s]*?}/i', $key, $matches, PREG_SET_ORDER);
294
            foreach ($matches as $match) {
0 ignored issues
show
Bug introduced by
The expression $matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
295
                if (array_key_exists($match[1], $this->bindings)) {
296
                    $key = preg_replace('/' . $match[0] . '/', $this->bindings[$match[1]], $key, 1);
297
                }
298
            }
299
        }
300
301
        return $key;
302
    }
303
304
    /**
305
     * Resolves an array of menu items properties.
306
     *
307
     * @param  array  &$items
308
     * @return void
309
     */
310 3
    protected function resolveItems(array &$items)
311
    {
312
        $resolver = function ($property) {
313
            return $this->resolve($property) ?: $property;
314 3
        };
315
316 3
        $totalItems = count($items);
317 3
        for ($i = 0; $i < $totalItems; $i++) {
318
            $items[$i]->fill(array_map($resolver, $items[$i]->getProperties()));
319
        }
320 3
    }
321
322
    /**
323
     * Add new child menu.
324
     *
325
     * @param array $attributes
326
     *
327
     * @return \Nwidart\Menus\MenuItem
328
     */
329
    public function add(array $attributes = array())
330
    {
331
        $item = MenuItem::make($attributes);
332
333
        $this->items[] = $item;
334
335
        return $item;
336
    }
337
338
    /**
339
     * Create new menu with dropdown.
340
     *
341
     * @param $title
342
     * @param callable $callback
343
     * @param array    $attributes
344
     *
345
     * @return $this
346
     */
347 View Code Duplication
    public function dropdown($title, \Closure $callback, $order = null, array $attributes = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
348
    {
349
        $properties = compact('title', 'order', 'attributes');
350
351
        if (func_num_args() == 3) {
352
            $arguments = func_get_args();
353
354
            $title = array_get($arguments, 0);
355
            $attributes = array_get($arguments, 2);
356
357
            $properties = compact('title', 'attributes');
358
        }
359
360
        $item = MenuItem::make($properties);
361
362
        call_user_func($callback, $item);
363
364
        $this->items[] = $item;
365
366
        return $item;
367
    }
368
369
    /**
370
     * Register new menu item using registered route.
371
     *
372
     * @param $route
373
     * @param $title
374
     * @param array $parameters
375
     * @param array $attributes
376
     *
377
     * @return static
378
     */
379
    public function route($route, $title, $parameters = array(), $order = null, $attributes = array())
380
    {
381
        if (func_num_args() == 4) {
382
            $arguments = func_get_args();
383
384
            return $this->add([
385
                'route' => [array_get($arguments, 0), array_get($arguments, 2)],
386
                'title' => array_get($arguments, 1),
387
                'attributes' => array_get($arguments, 3),
388
            ]);
389
        }
390
391
        $route = array($route, $parameters);
392
393
        $item = MenuItem::make(
394
            compact('route', 'title', 'parameters', 'attributes', 'order')
395
        );
396
397
        $this->items[] = $item;
398
399
        return $item;
400
    }
401
402
    /**
403
     * Format URL.
404
     *
405
     * @param string $url
406
     *
407
     * @return string
408
     */
409 2
    protected function formatUrl($url)
410
    {
411 2
        $uri = !is_null($this->prefixUrl) ? $this->prefixUrl . $url : $url;
412
413 2
        return $uri == '/' ? '/' : ltrim(rtrim($uri, '/'), '/');
414
    }
415
416
    /**
417
     * Register new menu item using url.
418
     *
419
     * @param $url
420
     * @param $title
421
     * @param array $attributes
422
     *
423
     * @return static
424
     */
425 2
    public function url($url, $title, $order = 0, $attributes = array())
426
    {
427 2 View Code Duplication
        if (func_num_args() == 3) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
428
            $arguments = func_get_args();
429
430
            return $this->add([
431
                'url' => $this->formatUrl(array_get($arguments, 0)),
432
                'title' => array_get($arguments, 1),
433
                'attributes' => array_get($arguments, 2),
434
            ]);
435
        }
436
437 2
        $url = $this->formatUrl($url);
438
439 2
        $item = MenuItem::make(compact('url', 'title', 'order', 'attributes'));
440
441 2
        $this->items[] = $item;
442
443 2
        return $item;
444
    }
445
446
    /**
447
     * Add new divider item.
448
     *
449
     * @param int $order
450
     * @return \Nwidart\Menus\MenuItem
451
     */
452
    public function addDivider($order = null)
453
    {
454
        $this->items[] = new MenuItem(array('name' => 'divider', 'order' => $order));
455
456
        return $this;
457
    }
458
459
    /**
460
     * Add new header item.
461
     *
462
     * @return \Nwidart\Menus\MenuItem
463
     */
464
    public function addHeader($title, $order = null)
465
    {
466
        $this->items[] = new MenuItem(array(
467
            'name' => 'header',
468
            'title' => $title,
469
            'order' => $order,
470
        ));
471
472
        return $this;
473
    }
474
475
    /**
476
     * Alias for "addHeader" method.
477
     *
478
     * @param string $title
479
     *
480
     * @return $this
481
     */
482
    public function header($title)
483
    {
484
        return $this->addHeader($title);
485
    }
486
487
    /**
488
     * Alias for "addDivider" method.
489
     *
490
     * @return $this
491
     */
492
    public function divider()
493
    {
494
        return $this->addDivider();
495
    }
496
497
    /**
498
     * Get items count.
499
     *
500
     * @return int
501
     */
502 1
    public function count()
503
    {
504 1
        return count($this->items);
505
    }
506
507
    /**
508
     * Empty the current menu items.
509
     */
510
    public function destroy()
511
    {
512
        $this->items = array();
513
514
        return $this;
515
    }
516
517
    /**
518
     * Render the menu to HTML tag.
519
     *
520
     * @param string $presenter
521
     *
522
     * @return string
523
     */
524 3
    public function render($presenter = null)
525
    {
526 3
        $this->resolveItems($this->items);
527
528 3
        if (!is_null($this->view)) {
529
            return $this->renderView($presenter);
530
        }
531
532 3
        if ($this->hasStyle($presenter)) {
533
            $this->setPresenterFromStyle($presenter);
534
        }
535
536 3
        if (!is_null($presenter) && !$this->hasStyle($presenter)) {
537
            $this->setPresenter($presenter);
538
        }
539
540 3
        return $this->renderMenu();
541
    }
542
543
    /**
544
     * Render menu via view presenter.
545
     *
546
     * @return \Illuminate\View\View
547
     */
548
    public function renderView($presenter = null)
549
    {
550
        return $this->views->make($presenter ?: $this->view, [
551
            'items' => $this->getOrderedItems(),
552
        ]);
553
    }
554
555
    /**
556
     * Get original items.
557
     *
558
     * @return array
559
     */
560
    public function getItems()
561
    {
562
        return $this->items;
563
    }
564
565
    /**
566
     * Get menu items as laravel collection instance.
567
     *
568
     * @return \Illuminate\Support\Collection
569
     */
570
    public function toCollection()
571
    {
572
        return collect($this->items);
573
    }
574
575
    /**
576
     * Get menu items as array.
577
     *
578
     * @return array
579
     */
580
    public function toArray()
581
    {
582
        return $this->toCollection()->toArray();
583
    }
584
585
    /**
586
     * Enable menu ordering.
587
     *
588
     * @return self
589
     */
590
    public function enableOrdering()
591
    {
592
        $this->ordering = true;
593
594
        return $this;
595
    }
596
597
    /**
598
     * Disable menu ordering.
599
     *
600
     * @return self
601
     */
602
    public function disableOrdering()
603
    {
604
        $this->ordering = false;
605
606
        return $this;
607
    }
608
609
    /**
610
     * Get menu items and order it by 'order' key.
611
     *
612
     * @return array
613
     */
614 3
    public function getOrderedItems()
615
    {
616 3
        if (config('menus.ordering') || $this->ordering) {
617
            return $this->toCollection()->sortBy(function ($item) {
618
                return $item->order;
619
            })->all();
620
        }
621
622 3
        return $this->items;
623
    }
624
625
    /**
626
     * Render the menu.
627
     *
628
     * @return string
629
     */
630 3
    protected function renderMenu()
631
    {
632 3
        $presenter = $this->getPresenter();
633 3
        $menu = $presenter->getOpenTagWrapper();
634
635 3
        foreach ($this->getOrderedItems() as $item) {
636
            if ($item->hidden()) {
637
                continue;
638
            }
639
640
            if ($item->hasSubMenu()) {
641
                $menu .= $presenter->getMenuWithDropDownWrapper($item);
642
            } elseif ($item->isHeader()) {
643
                $menu .= $presenter->getHeaderWrapper($item);
644
            } elseif ($item->isDivider()) {
645
                $menu .= $presenter->getDividerWrapper();
646
            } else {
647
                $menu .= $presenter->getMenuWithoutDropdownWrapper($item);
648
            }
649 3
        }
650
651 3
        $menu .= $presenter->getCloseTagWrapper();
652
653 3
        return $menu;
654
    }
655
}
656