Completed
Push — master ( 1de9b7...830752 )
by Kristof
38:46 queued 24:09
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 = null;
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 = null;
61
62
    /**
63
     * @var NodeMenuItem[]
64
     */
65
    private $breadCrumb = null;
66
67
    /**
68
     * @var Node[]
69
     */
70
    private $allNodes = array();
71
72
    /**
73
     * @var Node[]
74
     */
75
    private $childNodes = array();
76
77
    /**
78
     * @var Node[]
79
     */
80
    private $nodesByInternalName = array();
81
82
    /**
83
     * @var bool
84
     */
85
    private $initialized = false;
86
87
    /**
88
     * @var NodeMenuItem
89
     */
90
    private $rootNodeMenuItem = null;
91
92
    /**
93
     * @var DomainConfigurationInterface
94
     */
95
    private $domainConfiguration = null;
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 = array();
174
        $this->breadCrumb = null;
175
        $this->childNodes = array();
176
        $this->topNodeMenuItems = null;
177
        $this->nodesByInternalName = array();
178
179
        /* @var NodeRepository $repo */
180
        $repo = $this->em->getRepository('KunstmaanNodeBundle:Node');
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 = array();
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(
226
                            ) && !$includeHiddenFromNav
227
                        ) {
228
                            return false;
229
                        }
230
231
                        return true;
232
                    }
233
                );
234
            }
235
        }
236
237
        return $this->topNodeMenuItems;
238
    }
239
240
    /**
241
     * @return NodeMenuItem[]
242
     */
243
    public function getBreadCrumb()
244
    {
245
        $this->init();
246
        if (!is_array($this->breadCrumb)) {
247
            $this->breadCrumb = array();
248
249
            /* @var NodeRepository $repo */
250
            $repo = $this->em->getRepository('KunstmaanNodeBundle:Node');
251
252
            // Generate breadcrumb MenuItems - fetch *all* languages so you can link translations if needed
253
            $parentNodes = $repo->getAllParents($this->currentNode);
254
            $parentNodeMenuItem = null;
255
            /* @var Node $parentNode */
256
            foreach ($parentNodes as $parentNode) {
257
                $nodeTranslation = $parentNode->getNodeTranslation(
258
                    $this->locale,
259
                    $this->includeOffline
260
                );
261
                if (!is_null($nodeTranslation)) {
262
                    $nodeMenuItem = new NodeMenuItem(
263
                        $parentNode,
264
                        $nodeTranslation,
265
                        $parentNodeMenuItem,
266
                        $this
267
                    );
268
                    $this->breadCrumb[] = $nodeMenuItem;
269
                    $parentNodeMenuItem = $nodeMenuItem;
270
                }
271
            }
272
        }
273
274
        return $this->breadCrumb;
275
    }
276
277
    /**
278
     * @return NodeMenuItem|null
279
     */
280 View Code Duplication
    public function getCurrent()
281
    {
282
        $this->init();
283
        $breadCrumb = $this->getBreadCrumb();
284
        if (count($breadCrumb) > 0) {
285
            return $breadCrumb[count($breadCrumb) - 1];
286
        }
287
288
        return null;
289
    }
290
291
    /**
292
     * @param int $depth
293
     *
294
     * @return NodeMenuItem|null
295
     */
296 View Code Duplication
    public function getActiveForDepth($depth)
297
    {
298
        $breadCrumb = $this->getBreadCrumb();
299
        if (count($breadCrumb) >= $depth) {
300
            return $breadCrumb[$depth - 1];
301
        }
302
303
        return null;
304
    }
305
306
    /**
307
     * @param Node $node
308
     * @param bool $includeHiddenFromNav
309
     *
310
     * @return NodeMenuItem[]
311
     */
312
    public function getChildren(Node $node, $includeHiddenFromNav = true)
313
    {
314
        $this->init();
315
        $children = array();
316
317
        if (array_key_exists($node->getId(), $this->childNodes)) {
318
            $nodes = $this->childNodes[$node->getId()];
319
            /* @var Node $childNode */
320 View Code Duplication
            foreach ($nodes as $childNode) {
321
                $nodeTranslation = $childNode->getNodeTranslation(
322
                    $this->locale,
323
                    $this->includeOffline
324
                );
325
                if (!is_null($nodeTranslation)) {
326
                    $children[] = new NodeMenuItem(
327
                        $childNode,
328
                        $nodeTranslation,
329
                        false,
330
                        $this
331
                    );
332
                }
333
            }
334
335
            $children = array_filter(
336
                $children,
337 View Code Duplication
                function (NodeMenuItem $entry) use ($includeHiddenFromNav) {
338
                    if ($entry->getNode()->isHiddenFromNav(
339
                        ) && !$includeHiddenFromNav
340
                    ) {
341
                        return false;
342
                    }
343
344
                    return true;
345
                }
346
            );
347
        }
348
349
        return $children;
350
    }
351
352
    /**
353
     * @param \Kunstmaan\NodeBundle\Entity\Node $node
354
     * @param bool                              $includeHiddenFromNav
355
     *
356
     * @return array|\Kunstmaan\NodeBundle\Helper\NodeMenuItem[]
357
     */
358
    public function getSiblings(Node $node, $includeHiddenFromNav = true)
359
    {
360
        $this->init();
361
        $siblings = array();
362
363
        if (false !== $parent = $this->getParent($node)) {
364
            $siblings = $this->getChildren($parent, $includeHiddenFromNav);
365
366
            foreach ($siblings as $index => $child) {
367
                if ($child === $node) {
368
                    unset($siblings[$index]);
369
                }
370
            }
371
        }
372
373
        return $siblings;
374
    }
375
376
    /**
377
     * @param \Kunstmaan\NodeBundle\Entity\Node $node
378
     * @param bool                              $includeHiddenFromNav
379
     *
380
     * @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...
381
     */
382
    public function getPreviousSibling(Node $node, $includeHiddenFromNav = true)
383
    {
384
        $this->init();
385
386
        if (false !== $parent = $this->getParent($node)) {
387
            $siblings = $this->getChildren($parent, $includeHiddenFromNav);
388
389
            foreach ($siblings as $index => $child) {
390
                if ($child->getNode() === $node && ($index - 1 >= 0)) {
391
                    return $siblings[$index - 1];
392
                }
393
            }
394
        }
395
396
        return false;
397
    }
398
399
    /**
400
     * @param \Kunstmaan\NodeBundle\Entity\Node $node
401
     * @param bool                              $includeHiddenFromNav
402
     *
403
     * @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...
404
     */
405
    public function getNextSibling(Node $node, $includeHiddenFromNav = true)
406
    {
407
        $this->init();
408
409
        if (false !== $parent = $this->getParent($node)) {
410
            $siblings = $this->getChildren($parent, $includeHiddenFromNav);
411
412
            foreach ($siblings as $index => $child) {
413
                if ($child->getNode() === $node && (($index + 1) < count(
414
                            $siblings
415
                        ))
416
                ) {
417
                    return $siblings[$index + 1];
418
                }
419
            }
420
        }
421
422
        return false;
423
    }
424
425
    /**
426
     * @param Node $node
427
     *
428
     * @return NodeMenuItem
429
     */
430
    public function getParent(Node $node)
431
    {
432
        $this->init();
433
        if ($node->getParent() && array_key_exists(
434
                $node->getParent()->getId(),
435
                $this->allNodes
436
            )
437
        ) {
438
            return $this->allNodes[$node->getParent()->getId()];
439
        }
440
441
        return false;
442
    }
443
444
    /**
445
     * @param NodeTranslation $parentNode The parent node
446
     * @param string          $slug       The slug
447
     *
448
     * @return NodeTranslation
449
     */
450
    public function getNodeBySlug(NodeTranslation $parentNode, $slug)
451
    {
452
        return $this->em->getRepository('KunstmaanNodeBundle:NodeTranslation')
453
            ->getNodeTranslationForSlug($slug, $parentNode);
454
    }
455
456
    /**
457
     * @param string                                        $internalName   The
458
     *                                                                      internal
459
     *                                                                      name
460
     * @param NodeTranslation|NodeMenuItem|HasNodeInterface $parent         The
461
     *                                                                      parent
462
     * @param bool                                          $includeOffline
463
     *
464
     * @return NodeMenuItem|null
465
     */
466
    public function getNodeByInternalName(
467
        $internalName,
468
        $parent = null,
469
        $includeOffline = null
470
    ) {
471
        $this->init();
472
        $resultNode = null;
473
474
        if (is_null($includeOffline)) {
475
            $includeOffline = $this->includeOffline;
476
        }
477
478
        if (array_key_exists($internalName, $this->nodesByInternalName)) {
479
            $nodes = $this->nodesByInternalName[$internalName];
480
            $nodes = array_filter(
481
                $nodes,
482
                function (Node $entry) use ($includeOffline) {
483
                    if ($entry->isDeleted() && !$includeOffline) {
484
                        return false;
485
                    }
486
487
                    return true;
488
                }
489
            );
490
491
            if (!is_null($parent)) {
492
                /** @var Node $parentNode */
493
                if ($parent instanceof NodeTranslation) {
494
                    $parentNode = $parent->getNode();
495
                } elseif ($parent instanceof NodeMenuItem) {
496
                    $parentNode = $parent->getNode();
497
                } elseif ($parent instanceof HasNodeInterface) {
498
                    $repo = $this->em->getRepository(
499
                        'KunstmaanNodeBundle:Node'
500
                    );
501
                    $parentNode = $repo->getNodeFor($parent);
502
                }
503
504
                // Look for a node with the same parent id
505
                /** @var Node $node */
506
                foreach ($nodes as $node) {
507
                    if ($node->getParent()->getId() == $parentNode->getId()) {
508
                        $resultNode = $node;
509
510
                        break;
511
                    }
512
                }
513
514
                // Look for a node that has an ancestor with the same parent id
515
                if (is_null($resultNode)) {
516
                    /* @var Node $n */
517
                    foreach ($nodes as $node) {
518
                        $tempNode = $node;
519
                        while (is_null($resultNode) && !is_null(
520
                                $tempNode->getParent()
521
                            )) {
522
                            $tempParent = $tempNode->getParent();
523
                            if ($tempParent->getId() == $parentNode->getId()) {
524
                                $resultNode = $node;
525
526
                                break;
527
                            }
528
                            $tempNode = $tempParent;
529
                        }
530
                    }
531
                }
532
            } else {
533
                if (count($nodes) > 0) {
534
                    $resultNode = $nodes[0];
535
                }
536
            }
537
        }
538
539
        if ($resultNode) {
540
            $nodeTranslation = $resultNode->getNodeTranslation(
541
                $this->locale,
542
                $includeOffline
543
            );
544
            if (!is_null($nodeTranslation)) {
545
                return new NodeMenuItem(
546
                    $resultNode,
547
                    $nodeTranslation,
548
                    false,
549
                    $this
550
                );
551
            }
552
        }
553
554
        return null;
555
    }
556
557
    /**
558
     * Returns the current root node menu item
559
     */
560
    public function getRootNodeMenuItem()
561
    {
562
        if (is_null($this->rootNodeMenuItem)) {
563
            $rootNode = $this->domainConfiguration->getRootNode();
564
            if (!is_null($rootNode)) {
565
                $nodeTranslation = $rootNode->getNodeTranslation(
566
                    $this->locale,
567
                    $this->includeOffline
568
                );
569
                $this->rootNodeMenuItem = new NodeMenuItem(
570
                    $rootNode,
571
                    $nodeTranslation,
572
                    false,
573
                    $this
574
                );
575
            } else {
576
                $this->rootNodeMenuItem = $this->breadCrumb[0];
577
            }
578
        }
579
580
        return $this->rootNodeMenuItem;
581
    }
582
583
    /**
584
     * @return bool
585
     */
586
    public function isIncludeOffline()
587
    {
588
        return $this->includeOffline;
589
    }
590
591
    /**
592
     * @return string
593
     */
594
    public function getPermission()
595
    {
596
        return $this->permission;
597
    }
598
599
    /**
600
     * @return BaseUser
601
     */
602
    public function getUser()
603
    {
604
        return $this->tokenStorage->getToken()->getUser();
605
    }
606
607
    /**
608
     * @return EntityManagerInterface
609
     */
610
    public function getEntityManager()
611
    {
612
        return $this->em;
613
    }
614
615
    /**
616
     * @return TokenStorageInterface
617
     */
618
    public function getTokenStorage()
619
    {
620
        return $this->tokenStorage;
621
    }
622
623
    /**
624
     * @return AclHelper
625
     */
626
    public function getAclHelper()
627
    {
628
        return $this->aclHelper;
629
    }
630
631
    /**
632
     * @return string
633
     */
634
    public function getLocale()
635
    {
636
        return $this->locale;
637
    }
638
639
    /**
640
     * @return bool
641
     */
642
    public function isIncludeHiddenFromNav()
643
    {
644
        return $this->includeHiddenFromNav;
645
    }
646
647
    /**
648
     * Check if provided slug is in active path
649
     *
650
     * @param string $slug
651
     *
652
     * @return bool
653
     */
654
    public function getActive($slug)
655
    {
656
        $bc = $this->getBreadCrumb();
657
        foreach ($bc as $bcItem) {
658
            if ($bcItem->getSlug() == $slug) {
659
                return true;
660
            }
661
        }
662
663
        return false;
664
    }
665
666
    /**
667
     * @return bool
668
     */
669
    public function isInitialized()
670
    {
671
        return $this->initialized;
672
    }
673
674
    /**
675
     * @return array
676
     */
677
    private function getTopNodeMenuItems()
678
    {
679
        $topNodeMenuItems = array();
680
        $topNodes = $this->childNodes[0];
681
        /* @var Node $topNode */
682 View Code Duplication
        foreach ($topNodes as $topNode) {
683
            $nodeTranslation = $topNode->getNodeTranslation(
684
                $this->locale,
685
                $this->includeOffline
686
            );
687
            if (!is_null($nodeTranslation)) {
688
                $topNodeMenuItems[] = new NodeMenuItem(
689
                    $topNode,
690
                    $nodeTranslation,
691
                    null,
692
                    $this
693
                );
694
            }
695
        }
696
697
        return $topNodeMenuItems;
698
    }
699
}
700