Completed
Pull Request — 5.6 (#2830)
by Jeroen
14:14
created

src/Kunstmaan/NodeBundle/Helper/NodeMenu.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Kunstmaan\NodeBundle\Helper;
4
5
use Doctrine\ORM\EntityManagerInterface;
6
use Kunstmaan\AdminBundle\Helper\DomainConfigurationInterface;
7
use Kunstmaan\AdminBundle\Helper\Security\Acl\AclHelper;
8
use Kunstmaan\AdminBundle\Helper\Security\Acl\Permission\PermissionMap;
9
use Kunstmaan\NodeBundle\Entity\HasNodeInterface;
10
use Kunstmaan\NodeBundle\Entity\Node;
11
use Kunstmaan\NodeBundle\Entity\NodeTranslation;
12
use Kunstmaan\NodeBundle\Repository\NodeRepository;
13
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
14
15
class NodeMenu
16
{
17
    /**
18
     * @var EntityManagerInterface
19
     */
20
    private $em;
21
22
    /**
23
     * @var TokenStorageInterface
24
     */
25
    private $tokenStorage;
26
27
    /**
28
     * @var AclHelper
29
     */
30
    private $aclHelper;
31
32
    /**
33
     * @var string
34
     */
35
    private $locale;
36
37
    /**
38
     * @var Node
39
     */
40
    private $currentNode;
41
42
    /**
43
     * @var string
44
     */
45
    private $permission = PermissionMap::PERMISSION_VIEW;
46
47
    /**
48
     * @var bool
49
     */
50
    private $includeOffline = false;
51
52
    /**
53
     * @var bool
54
     */
55
    private $includeHiddenFromNav = false;
56
57
    /**
58
     * @var NodeMenuItem[]
59
     */
60
    private $topNodeMenuItems;
61
62
    /**
63
     * @var NodeMenuItem[]
64
     */
65
    private $breadCrumb;
66
67
    /**
68
     * @var Node[]
69
     */
70
    private $allNodes = [];
71
72
    /**
73
     * @var Node[]
74
     */
75
    private $childNodes = [];
76
77
    /**
78
     * @var Node[]
79
     */
80
    private $nodesByInternalName = [];
81
82
    /**
83
     * @var bool
84
     */
85
    private $initialized = false;
86
87
    /**
88
     * @var NodeMenuItem
89
     */
90
    private $rootNodeMenuItem;
91
92
    /**
93
     * @var DomainConfigurationInterface
94
     */
95
    private $domainConfiguration;
96
97
    /**
98
     * @param EntityManagerInterface       $em                  The entity manager
99
     * @param TokenStorageInterface        $tokenStorage        The security token storage
100
     * @param AclHelper                    $aclHelper           The ACL helper pages
101
     * @param DomainConfigurationInterface $domainConfiguration The current domain configuration
102
     */
103
    public function __construct(
104
        EntityManagerInterface $em,
105
        TokenStorageInterface $tokenStorage,
106
        AclHelper $aclHelper,
107
        DomainConfigurationInterface $domainConfiguration
108
    ) {
109
        $this->em = $em;
110
        $this->tokenStorage = $tokenStorage;
111
        $this->aclHelper = $aclHelper;
112
        $this->domainConfiguration = $domainConfiguration;
113
    }
114
115
    /**
116
     * @param string $locale
117
     */
118
    public function setLocale($locale)
119
    {
120
        $this->locale = $locale;
121
    }
122
123
    /**
124
     * @param Node $currentNode
125
     */
126
    public function setCurrentNode(Node $currentNode = null)
127
    {
128
        $this->currentNode = $currentNode;
129
    }
130
131
    /**
132
     * @param string $permission
133
     */
134
    public function setPermission($permission)
135
    {
136
        if ($this->permission !== $permission) {
137
            // For now reset initialized flag when cached data has to be reset ...
138
            $this->initialized = false;
139
        }
140
        $this->permission = $permission;
141
    }
142
143
    /**
144
     * @param bool $includeOffline
145
     */
146
    public function setIncludeOffline($includeOffline)
147
    {
148
        $this->includeOffline = $includeOffline;
149
    }
150
151
    /**
152
     * @param bool $includeHiddenFromNav
153
     */
154
    public function setIncludeHiddenFromNav($includeHiddenFromNav)
155
    {
156
        if ($this->includeHiddenFromNav !== $includeHiddenFromNav) {
157
            // For now reset initialized flag when cached data has to be reset ...
158
            $this->initialized = false;
159
        }
160
        $this->includeHiddenFromNav = $includeHiddenFromNav;
161
    }
162
163
    /**
164
     * This method initializes the nodemenu only once, the method may be
165
     * executed multiple times
166
     */
167
    private function init()
168
    {
169
        if ($this->initialized) {
170
            return;
171
        }
172
173
        $this->allNodes = [];
174
        $this->breadCrumb = null;
175
        $this->childNodes = [];
176
        $this->topNodeMenuItems = null;
177
        $this->nodesByInternalName = [];
178
179
        /* @var NodeRepository $repo */
180
        $repo = $this->em->getRepository(Node::class);
181
182
        // Get all possible menu items in one query (also fetch offline nodes)
183
        $nodes = $repo->getChildNodes(
184
            false,
185
            $this->locale,
186
            $this->permission,
187
            $this->aclHelper,
188
            $this->includeHiddenFromNav,
189
            true,
190
            $this->domainConfiguration->getRootNode()
191
        );
192
        foreach ($nodes as $node) {
193
            $this->allNodes[$node->getId()] = $node;
194
195
            if ($node->getParent()) {
196
                $this->childNodes[$node->getParent()->getId()][] = $node;
197
            } else {
198
                $this->childNodes[0][] = $node;
199
            }
200
            $internalName = $node->getInternalName();
201
            if ($internalName) {
202
                $this->nodesByInternalName[$internalName][] = $node;
203
            }
204
        }
205
        $this->initialized = true;
206
    }
207
208
    /**
209
     * @return NodeMenuItem[]
210
     */
211
    public function getTopNodes()
212
    {
213
        $this->init();
214
        if (!\is_array($this->topNodeMenuItems)) {
215
            $this->topNodeMenuItems = [];
216
217
            // To be backwards compatible we need to create the top node MenuItems
218
            if (\array_key_exists(0, $this->childNodes)) {
219
                $topNodeMenuItems = $this->getTopNodeMenuItems();
220
221
                $includeHiddenFromNav = $this->includeHiddenFromNav;
222
                $this->topNodeMenuItems = array_filter(
223
                    $topNodeMenuItems,
224 View Code Duplication
                    function (NodeMenuItem $entry) use ($includeHiddenFromNav) {
225
                        if ($entry->getNode()->isHiddenFromNav() && !$includeHiddenFromNav) {
226
                            return false;
227
                        }
228
229
                        return true;
230
                    }
231
                );
232
            }
233
        }
234
235
        return $this->topNodeMenuItems;
236
    }
237
238
    /**
239
     * @return NodeMenuItem[]
240
     */
241
    public function getBreadCrumb()
242
    {
243
        $this->init();
244
        if (!\is_array($this->breadCrumb)) {
245
            $this->breadCrumb = [];
246
247
            /* @var NodeRepository $repo */
248
            $repo = $this->em->getRepository(Node::class);
249
250
            // Generate breadcrumb MenuItems - fetch *all* languages so you can link translations if needed
251
            $parentNodes = $repo->getAllParents($this->currentNode);
252
            $parentNodeMenuItem = null;
253
            /* @var Node $parentNode */
254
            foreach ($parentNodes as $parentNode) {
255
                $nodeTranslation = $parentNode->getNodeTranslation(
256
                    $this->locale,
257
                    $this->includeOffline
258
                );
259
                if (!\is_null($nodeTranslation)) {
260
                    $nodeMenuItem = new NodeMenuItem(
261
                        $parentNode,
262
                        $nodeTranslation,
263
                        $parentNodeMenuItem,
264
                        $this
265
                    );
266
                    $this->breadCrumb[] = $nodeMenuItem;
267
                    $parentNodeMenuItem = $nodeMenuItem;
268
                }
269
            }
270
        }
271
272
        return $this->breadCrumb;
273
    }
274
275
    /**
276
     * @return NodeMenuItem|null
277
     */
278 View Code Duplication
    public function getCurrent()
279
    {
280
        $this->init();
281
        $breadCrumb = $this->getBreadCrumb();
282
        if (\count($breadCrumb) > 0) {
283
            return $breadCrumb[\count($breadCrumb) - 1];
284
        }
285
286
        return null;
287
    }
288
289
    /**
290
     * @param int $depth
291
     *
292
     * @return NodeMenuItem|null
293
     */
294 View Code Duplication
    public function getActiveForDepth($depth)
295
    {
296
        $breadCrumb = $this->getBreadCrumb();
297
        if (\count($breadCrumb) >= $depth) {
298
            return $breadCrumb[$depth - 1];
299
        }
300
301
        return null;
302
    }
303
304
    /**
305
     * @param bool $includeHiddenFromNav
306
     *
307
     * @return NodeMenuItem[]
308
     */
309
    public function getChildren(Node $node, $includeHiddenFromNav = true)
310
    {
311
        $this->init();
312
        $children = [];
313
314
        if (\array_key_exists($node->getId(), $this->childNodes)) {
315
            $nodes = $this->childNodes[$node->getId()];
316
            /* @var Node $childNode */
317 View Code Duplication
            foreach ($nodes as $childNode) {
318
                $nodeTranslation = $childNode->getNodeTranslation(
319
                    $this->locale,
320
                    $this->includeOffline
321
                );
322
                if (!\is_null($nodeTranslation)) {
323
                    $children[] = new NodeMenuItem(
324
                        $childNode,
325
                        $nodeTranslation,
326
                        false,
327
                        $this
328
                    );
329
                }
330
            }
331
332
            $children = array_filter(
333
                $children,
334 View Code Duplication
                function (NodeMenuItem $entry) use ($includeHiddenFromNav) {
335
                    if ($entry->getNode()->isHiddenFromNav() && !$includeHiddenFromNav) {
336
                        return false;
337
                    }
338
339
                    return true;
340
                }
341
            );
342
        }
343
344
        return $children;
345
    }
346
347
    /**
348
     * @param bool $includeHiddenFromNav
349
     *
350
     * @return array|\Kunstmaan\NodeBundle\Helper\NodeMenuItem[]
351
     */
352
    public function getSiblings(Node $node, $includeHiddenFromNav = true)
353
    {
354
        $this->init();
355
        $siblings = [];
356
357
        if (false !== $parent = $this->getParent($node)) {
358
            $siblings = $this->getChildren($parent, $includeHiddenFromNav);
359
360
            foreach ($siblings as $index => $child) {
361
                if ($child === $node) {
362
                    unset($siblings[$index]);
363
                }
364
            }
365
        }
366
367
        return $siblings;
368
    }
369
370
    /**
371
     * @param bool $includeHiddenFromNav
372
     *
373
     * @return bool|\Kunstmaan\NodeBundle\Helper\NodeMenuItem
0 ignored issues
show
Consider making the return type a bit more specific; maybe use NodeMenuItem|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
374
     */
375
    public function getPreviousSibling(Node $node, $includeHiddenFromNav = true)
376
    {
377
        $this->init();
378
379
        if (false !== $parent = $this->getParent($node)) {
380
            $siblings = $this->getChildren($parent, $includeHiddenFromNav);
381
382
            foreach ($siblings as $index => $child) {
383
                if ($child->getNode() === $node && ($index - 1 >= 0)) {
384
                    return $siblings[$index - 1];
385
                }
386
            }
387
        }
388
389
        return false;
390
    }
391
392
    /**
393
     * @param bool $includeHiddenFromNav
394
     *
395
     * @return bool|\Kunstmaan\NodeBundle\Helper\NodeMenuItem
0 ignored issues
show
Consider making the return type a bit more specific; maybe use NodeMenuItem|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
396
     */
397
    public function getNextSibling(Node $node, $includeHiddenFromNav = true)
398
    {
399
        $this->init();
400
401
        if (false !== $parent = $this->getParent($node)) {
402
            $siblings = $this->getChildren($parent, $includeHiddenFromNav);
403
404
            foreach ($siblings as $index => $child) {
405
                if ($child->getNode() === $node && (($index + 1) < \count(
406
                            $siblings
407
                        ))
408
                ) {
409
                    return $siblings[$index + 1];
410
                }
411
            }
412
        }
413
414
        return false;
415
    }
416
417
    /**
418
     * @return NodeMenuItem
419
     */
420
    public function getParent(Node $node)
421
    {
422
        $this->init();
423
        if ($node->getParent() && \array_key_exists(
424
                $node->getParent()->getId(),
425
                $this->allNodes
426
            )
427
        ) {
428
            return $this->allNodes[$node->getParent()->getId()];
429
        }
430
431
        return false;
432
    }
433
434
    /**
435
     * @param NodeTranslation $parentNode The parent node
436
     * @param string          $slug       The slug
437
     *
438
     * @return NodeTranslation
439
     */
440
    public function getNodeBySlug(NodeTranslation $parentNode, $slug)
441
    {
442
        return $this->em->getRepository(NodeTranslation::class)
443
            ->getNodeTranslationForSlug($slug, $parentNode);
444
    }
445
446
    /**
447
     * @param string                                        $internalName   The
448
     *                                                                      internal
449
     *                                                                      name
450
     * @param NodeTranslation|NodeMenuItem|HasNodeInterface $parent         The
451
     *                                                                      parent
452
     * @param bool                                          $includeOffline
453
     *
454
     * @return NodeMenuItem|null
455
     */
456
    public function getNodeByInternalName(
457
        $internalName,
458
        $parent = null,
459
        $includeOffline = null
460
    ) {
461
        $this->init();
462
        $resultNode = null;
463
464
        if (\is_null($includeOffline)) {
465
            $includeOffline = $this->includeOffline;
466
        }
467
468
        if (\array_key_exists($internalName, $this->nodesByInternalName)) {
469
            $nodes = $this->nodesByInternalName[$internalName];
470
            $nodes = array_filter(
471
                $nodes,
472
                function (Node $entry) use ($includeOffline) {
473
                    if ($entry->isDeleted() && !$includeOffline) {
474
                        return false;
475
                    }
476
477
                    return true;
478
                }
479
            );
480
481
            if (!\is_null($parent)) {
482
                $parentNode = null;
483
                /** @var Node $parentNode */
484
                if ($parent instanceof NodeTranslation) {
485
                    $parentNode = $parent->getNode();
486
                } elseif ($parent instanceof NodeMenuItem) {
487
                    $parentNode = $parent->getNode();
488
                } elseif ($parent instanceof HasNodeInterface) {
489
                    $repo = $this->em->getRepository(
490
                        Node::class
491
                    );
492
                    $parentNode = $repo->getNodeFor($parent);
493
                }
494
495
                // Look for a node with the same parent id
496
                /** @var Node $node */
497
                foreach ($nodes as $node) {
498
                    if ($parentNode && $node->getParent()->getId() == $parentNode->getId()) {
499
                        $resultNode = $node;
500
501
                        break;
502
                    }
503
                }
504
505
                // Look for a node that has an ancestor with the same parent id
506
                if (\is_null($resultNode)) {
507
                    /* @var Node $n */
508
                    foreach ($nodes as $node) {
509
                        $tempNode = $node;
510
                        while (\is_null($resultNode) && !\is_null(
511
                                $tempNode->getParent()
512
                            )) {
513
                            $tempParent = $tempNode->getParent();
514
                            if ($parentNode && $tempParent->getId() == $parentNode->getId()) {
515
                                $resultNode = $node;
516
517
                                break;
518
                            }
519
                            $tempNode = $tempParent;
520
                        }
521
                    }
522
                }
523
            } elseif (\count($nodes) > 0) {
524
                $resultNode = $nodes[0];
525
            }
526
        }
527
528
        if ($resultNode) {
529
            $nodeTranslation = $resultNode->getNodeTranslation(
530
                $this->locale,
531
                $includeOffline
532
            );
533
            if (!\is_null($nodeTranslation)) {
534
                return new NodeMenuItem(
535
                    $resultNode,
536
                    $nodeTranslation,
537
                    false,
538
                    $this
539
                );
540
            }
541
        }
542
543
        return null;
544
    }
545
546
    /**
547
     * Returns the current root node menu item
548
     */
549
    public function getRootNodeMenuItem()
550
    {
551
        if (\is_null($this->rootNodeMenuItem)) {
552
            $rootNode = $this->domainConfiguration->getRootNode();
553
            if (!\is_null($rootNode)) {
554
                $nodeTranslation = $rootNode->getNodeTranslation(
555
                    $this->locale,
556
                    $this->includeOffline
557
                );
558
                $this->rootNodeMenuItem = new NodeMenuItem(
559
                    $rootNode,
560
                    $nodeTranslation,
561
                    false,
562
                    $this
563
                );
564
            } else {
565
                $this->rootNodeMenuItem = $this->breadCrumb[0];
566
            }
567
        }
568
569
        return $this->rootNodeMenuItem;
570
    }
571
572
    /**
573
     * @return bool
574
     */
575
    public function isIncludeOffline()
576
    {
577
        return $this->includeOffline;
578
    }
579
580
    /**
581
     * @return string
582
     */
583
    public function getPermission()
584
    {
585
        return $this->permission;
586
    }
587
588
    /**
589
     * @return BaseUser
590
     */
591
    public function getUser()
592
    {
593
        return $this->tokenStorage->getToken()->getUser();
594
    }
595
596
    /**
597
     * @return EntityManagerInterface
598
     */
599
    public function getEntityManager()
600
    {
601
        return $this->em;
602
    }
603
604
    /**
605
     * @return TokenStorageInterface
606
     */
607
    public function getTokenStorage()
608
    {
609
        return $this->tokenStorage;
610
    }
611
612
    /**
613
     * @return AclHelper
614
     */
615
    public function getAclHelper()
616
    {
617
        return $this->aclHelper;
618
    }
619
620
    /**
621
     * @return string
622
     */
623
    public function getLocale()
624
    {
625
        return $this->locale;
626
    }
627
628
    /**
629
     * @return bool
630
     */
631
    public function isIncludeHiddenFromNav()
632
    {
633
        return $this->includeHiddenFromNav;
634
    }
635
636
    /**
637
     * Check if provided slug is in active path
638
     *
639
     * @param string $slug
640
     *
641
     * @return bool
642
     */
643
    public function getActive($slug)
644
    {
645
        $bc = $this->getBreadCrumb();
646
        foreach ($bc as $bcItem) {
647
            if ($bcItem->getSlug() == $slug) {
648
                return true;
649
            }
650
        }
651
652
        return false;
653
    }
654
655
    /**
656
     * @return bool
657
     */
658
    public function isInitialized()
659
    {
660
        return $this->initialized;
661
    }
662
663
    /**
664
     * @return array
665
     */
666
    private function getTopNodeMenuItems()
667
    {
668
        $topNodeMenuItems = [];
669
        $topNodes = $this->childNodes[0];
670
        /* @var Node $topNode */
671 View Code Duplication
        foreach ($topNodes as $topNode) {
672
            $nodeTranslation = $topNode->getNodeTranslation(
673
                $this->locale,
674
                $this->includeOffline
675
            );
676
            if (!\is_null($nodeTranslation)) {
677
                $topNodeMenuItems[] = new NodeMenuItem(
678
                    $topNode,
679
                    $nodeTranslation,
680
                    null,
681
                    $this
682
                );
683
            }
684
        }
685
686
        return $topNodeMenuItems;
687
    }
688
}
689