Completed
Push — master ( 7cecbb...791ec7 )
by Oleg
07:00
created

Builder::getByIndex()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 6
ccs 2
cts 2
cp 1
rs 9.4285
cc 2
eloc 3
nc 2
nop 2
crap 2
1
<?php
2
namespace Malezha\Menu\Entity;
3
4
use Illuminate\Contracts\Config\Repository;
5
use Illuminate\Contracts\Container\Container;
6
use Illuminate\Http\Request;
7
use Malezha\Menu\Contracts\Attributes as AttributesContract;
8
use Malezha\Menu\Contracts\Builder as BuilderContract;
9
use Malezha\Menu\Contracts\SubMenu as GroupContract;
10
use Malezha\Menu\Contracts\Item as ItemContract;
11
use Malezha\Menu\Contracts\Link as LinkContract;
12
use Malezha\Menu\Contracts\MenuRender;
13
use Malezha\Menu\Contracts\SubMenu as SubMenuContract;
14
use Malezha\Menu\Traits\HasAttributes;
15
16
/**
17
 * Class Builder
18
 * @package Malezha\Menu\Entity
19
 */
20
class Builder implements BuilderContract
21
{
22
    use HasAttributes;
23
24
    /**
25
     * @var Container
26
     */
27
    protected $app;
28
29
    /**
30
     * @var array
31
     */
32
    protected $items;
33
34
    /**
35
     * @var array
36
     */
37
    protected $indexes = [];
38
39
    /**
40
     * @var string
41
     */
42
    protected $name;
43
44
    /**
45
     * @var string
46
     */
47
    protected $type;
48
49
    /**
50
     * @var AttributesContract
51
     */
52
    protected $activeAttributes;
53
54
    /**
55
     * @var MenuRender
56
     */
57
    protected $viewFactory;
58
59
    /**
60
     * @var array
61
     */
62
    protected $config;
63
64
    /**
65
     * @var string
66
     */
67
    protected $view = null;
68
    
69
    /**
70
     * @inheritDoc
71
     */
72 24
    public function __construct(Container $container, $name, AttributesContract $attributes,
73
                                AttributesContract $activeAttributes, $type = self::UL, $view = null)
74
    {
75 24
        $this->app = $container;
76 24
        $this->name = $name;
77 24
        $this->type = $type;
78 24
        $this->attributes = $attributes;
79 24
        $this->items = [];
80 24
        $this->activeAttributes = $activeAttributes;
81 24
        $this->viewFactory = $this->app->make(MenuRender::class);
82 24
        $this->config = $this->app->make(Repository::class)->get('menu');
83
        try {
84 24
            $this->setView($view);
85 24
        } catch (\Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
86 24
    }
87
88
    /**
89
     * @inheritDoc
90
     */
91 5
    public function submenu($name, \Closure $itemCallable, \Closure $menuCallable)
92
    {
93 5
        $link = $this->linkFactory($name);
94 5
        $item = $this->itemFactory($link, [], $itemCallable);
95 5
        $menu = $this->builderFactory($name, [], $this->activeAttributes()->all(), $menuCallable);
96
97 5
        $group = $this->app->make(GroupContract::class, [
98 5
            'menu' => $menu,
99 5
            'item' => $item,
100 5
        ]);
101
        
102 5
        $this->saveItem($name, $group);
103
104 5
        return $group;
105
    }
106
107
    /**
108
     * @inheritDoc
109
     */
110 16
    public function create($name, $title, $url, $attributes = [], $linkAttributes = [], $callback = null)
111
    {
112 16
        if ($this->has($name)) {
113 1
            throw new \RuntimeException("Duplicate menu key \"${name}\"");
114
        }
115
116
        $link = $this->linkFactory($title, $url, $linkAttributes);
117
        $item = $this->itemFactory($link, $attributes, $callback);
118
119
        $this->saveItem($name, $item);
120
121
        return $item;
122
    }
123
124
    /**
125
     * @inheritDoc
126
     */
127
    public function insertBefore($name, \Closure $callback)
128
    {
129 1
        $this->insert($this->indexes[$name], $this->prepareInsert($name, $callback));
130 1
    }
131
132
    /**
133
     * @inheritDoc
134
     */
135
    public function insertAfter($name, \Closure $callback)
136
    {
137 1
        $this->insert($this->indexes[$name] + 1, $this->prepareInsert($name, $callback));
138 1
    }
139
140
    /**
141
     * @inheritDoc
142
     */
143
    public function has($name)
144
    {
145 18
        return array_key_exists($name, $this->items);
146
    }
147
148
    /**
149
     * @inheritDoc
150
     */
151
    public function get($name, $default = null)
152
    {
153 4
        if ($this->has($name)) {
154 4
            return $this->items[$name];
155
        }
156 1
        return $default;
157
    }
158
159
    /**
160
     * @inheritDoc
161
     */
162
    public function getByIndex($index, $default = null)
163
    {
164 1
        $key = array_search($index, $this->indexes);
165
        
166 1
        return $key === false ? $default : $this->get($key, $default);
167
    }
168
169
    /**
170
     * @inheritDoc
171
     */
172
    public function all()
173
    {
174 8
        return $this->items;
175
    }
176
177
    /**
178
     * @inheritDoc
179
     */
180
    public function forget($name)
181
    {
182 1
        if ($this->has($name)) {
183 1
            unset($this->items[$name]);
184 1
        }
185 1
    }
186
187
    /**
188
     * @inheritDoc
189
     */
190
    public function getType()
191
    {
192 8
        return $this->type;
193
    }
194
195
    /**
196
     * @inheritDoc
197
     */
198
    public function setType($type)
199
    {
200 1
        $this->type = (string) $type;
201 1
    }
202
203
    /**
204
     * @inheritDoc
205
     */
206
    public function render($renderView = null)
207
    {
208 7
        $view = $this->getRenderView($renderView);
209
210 7
        $minify = $this->config['minify'];
211
212 7
        $rendered = $this->viewFactory->make($view)->with([
213 7
            'menu' => $this,
214 7
            'renderView' => $renderView,
215 7
        ])->render();
216
        
217 7
        if ($minify) {
218 7
            $rendered = $this->minifyHtmlOutput($rendered);
219 7
        }
220
        
221 7
        return $rendered;
222
    }
223
224
    /**
225
     * @inheritDoc
226
     */
227
    public function activeAttributes($callback = null)
228
    {
229 10
        if (is_callable($callback)) {
230 1
            return call_user_func($callback, $this->activeAttributes);
231
        }
232
233 10
        return $this->activeAttributes;
234
    }
235
236
    /**
237
     * @inheritDoc
238
     */
239
    public function getView()
240
    {
241 6
        return $this->view;
242
    }
243
244
    /**
245
     * @inheritDoc
246
     */
247
    public function setView($view)
248
    {
249 24
        if (!$this->viewFactory->exists($view)) {
250 24
            throw new \Exception('View not found');
251
        }
252
        
253 1
        $this->view = $view;
254 1
    }
255
256
    /**
257
     * Minify html
258
     *
259
     * @param string $html
260
     * @return string
261
     */
262
    protected function minifyHtmlOutput($html)
263
    {
264
        $search = array(
265 7
            '/\>[^\S]+/s', // strip whitespaces after tags, except space
266 7
            '/[^\S]+\</s', // strip whitespaces before tags, except space
267
            '/(\s)+/s'       // shorten multiple whitespace sequences
268 7
        );
269
270
        $replace = array(
271 7
            '>',
272 7
            '<',
273
            '\\1'
274 7
        );
275
276 7
        return preg_replace($search, $replace, $html);
277
    }
278
279
    /**
280
     * Get view for render
281
     * 
282
     * @param string $view
283
     * @return string
284
     */
285
    protected function getRenderView($view = null)
286
    {
287 7
        $renderView = $this->config['view'];
288
        
289 7
        if (!empty($this->view)) {
290 1
            $renderView = $this->view;
291 1
        }
292
        
293 7
        if (!empty($view) && $this->viewFactory->exists($view)) {
294 1
            $renderView = $view;
295 1
        }
296
        
297 7
        return $renderView;
298
    }
299
300
    /**
301
     * @param string $title
302
     * @param string $url
303
     * @param array $attributes
304
     * @return LinkContract
305
     */
306
    protected function linkFactory($title = '', $url = '#', $attributes = [])
307
    {
308 17
        return $this->app->make(LinkContract::class, [
309 17
            'title' => $title,
310 17
            'url' => $url,
311 17
            'attributes' => $this->app->make(AttributesContract::class, ['attributes' => $attributes]),
312 17
        ]);
313
    }
314
315
    /**
316
     * @param LinkContract $link
317
     * @param array $attributes
318
     * @param \Closure $callback
319
     * @return ItemContract
320
     */
321
    protected function itemFactory($link, $attributes = [], $callback = null)
322
    {
323 17
        $item = $this->app->make(ItemContract::class, [
324 17
            'builder' => $this,
325 17
            'attributes' => $this->app->make(AttributesContract::class, ['attributes' => $attributes]),
326 17
            'link' => $link,
327 17
            'request' => $this->app->make(Request::class),
328 17
        ]);
329
330 17
        if (is_callable($callback)) {
331 6
            call_user_func($callback, $item);
332 6
        }
333
        
334 17
        return $item;
335
    }
336
337
    /**
338
     * @param string $name
339
     * @param ItemContract|SubMenuContract $item
340
     */
341
    protected function saveItem($name, $item)
342
    {
343 17
        $this->items[$name] = $item;
344 17
        $this->indexes[$name] = count($this->items) - 1;
345 17
    }
346
347
    /**
348
     * @param string $name
349
     * @param array $attributes
350
     * @param array $activeAttributes
351
     * @param \Closure|null $callback
352
     * @return BuilderContract
353
     */
354
    protected function builderFactory($name, $attributes = [], $activeAttributes = [], $callback = null)
355
    {
356 6
        $menu = $this->app->make(BuilderContract::class, [
357 6
            'container' => $this->app,
358 6
            'name' => $name,
359 6
            'activeAttributes' => $this->app->make(AttributesContract::class, ['attributes' => $activeAttributes]),
360 6
            'attributes' => $this->app->make(AttributesContract::class, ['attributes' => $attributes]),
361 6
            'view' => $this->getView(),
362 6
        ]);
363
364 6
        if (is_callable($callback)) {
365 6
            call_user_func($callback, $menu);
366 6
        }
367
368 6
        return $menu;
369
    }
370
371
    protected function rebuildIndexesArray()
372
    {
373 1
        $this->indexes = [];
374 1
        $iterator = 0;
375
376 1
        foreach ($this->items as $key => $value) {
377 1
            $this->indexes[$key] = $iterator++;
378 1
        }
379 1
    }
380
381
    /**
382
     * @param string $name
383
     * @param \Closure $callback
384
     * @return array
385
     */
386
    protected function prepareInsert($name, $callback)
387
    {
388 1
        if (!$this->has($name)) {
389
            throw new \RuntimeException("Menu item \"${name}\" must be exists");
390
        }
391
392 1
        $forInsert = $this->builderFactory('tmp', [], [], $callback)->all();
393 1
        $diff = array_diff(array_keys(array_diff_key($this->items, $forInsert)), array_keys($this->items));
394
395 1
        if (count($diff) > 0) {
396
            throw new \RuntimeException('Duplicated keys: ' . implode(', ', array_keys($diff)));
397
        }
398
        
399 1
        return $forInsert;
400
    }
401
402
    /**
403
     * @param int $position
404
     * @param array $values
405
     */
406
    protected function insert($position, $values)
407
    {
408 1
        $firstArray = array_splice($this->items, 0, $position);
409 1
        $this->items = array_merge($firstArray, $values, $this->items);
410 1
        $this->rebuildIndexesArray();
411
    }
412
}