Completed
Push — master ( 6d6774...64f3ed )
by Jeroen
11:23 queued 05:13
created

src/Kunstmaan/NodeBundle/Helper/NodeMenu.php (1 issue)

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 = 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;
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 = 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() && !$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 = array();
246
247
            /* @var NodeRepository $repo */
248
            $repo = $this->em->getRepository('KunstmaanNodeBundle:Node');
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 Node $node
306
     * @param bool $includeHiddenFromNav
307
     *
308
     * @return NodeMenuItem[]
309
     */
310
    public function getChildren(Node $node, $includeHiddenFromNav = true)
311
    {
312
        $this->init();
313
        $children = array();
314
315
        if (\array_key_exists($node->getId(), $this->childNodes)) {
316
            $nodes = $this->childNodes[$node->getId()];
317
            /* @var Node $childNode */
318 View Code Duplication
            foreach ($nodes as $childNode) {
319
                $nodeTranslation = $childNode->getNodeTranslation(
320
                    $this->locale,
321
                    $this->includeOffline
322
                );
323
                if (!\is_null($nodeTranslation)) {
324
                    $children[] = new NodeMenuItem(
325
                        $childNode,
326
                        $nodeTranslation,
327
                        false,
328
                        $this
329
                    );
330
                }
331
            }
332
333
            $children = array_filter(
334
                $children,
335 View Code Duplication
                function (NodeMenuItem $entry) use ($includeHiddenFromNav) {
336
                    if ($entry->getNode()->isHiddenFromNav() && !$includeHiddenFromNav) {
337
                        return false;
338
                    }
339
340
                    return true;
341
                }
342
            );
343
        }
344
345
        return $children;
346
    }
347
348
    /**
349
     * @param \Kunstmaan\NodeBundle\Entity\Node $node
350
     * @param bool                              $includeHiddenFromNav
351
     *
352
     * @return array|\Kunstmaan\NodeBundle\Helper\NodeMenuItem[]
353
     */
354
    public function getSiblings(Node $node, $includeHiddenFromNav = true)
355
    {
356
        $this->init();
357
        $siblings = array();
358
359
        if (false !== $parent = $this->getParent($node)) {
360
            $siblings = $this->getChildren($parent, $includeHiddenFromNav);
361
362
            foreach ($siblings as $index => $child) {
363
                if ($child === $node) {
364
                    unset($siblings[$index]);
365
                }
366
            }
367
        }
368
369
        return $siblings;
370
    }
371
372
    /**
373
     * @param \Kunstmaan\NodeBundle\Entity\Node $node
374
     * @param bool                              $includeHiddenFromNav
375
     *
376
     * @return bool|\Kunstmaan\NodeBundle\Helper\NodeMenuItem
377
     */
378
    public function getPreviousSibling(Node $node, $includeHiddenFromNav = true)
379
    {
380
        $this->init();
381
382
        if (false !== $parent = $this->getParent($node)) {
383
            $siblings = $this->getChildren($parent, $includeHiddenFromNav);
384
385
            foreach ($siblings as $index => $child) {
386
                if ($child->getNode() === $node && ($index - 1 >= 0)) {
387
                    return $siblings[$index - 1];
388
                }
389
            }
390
        }
391
392
        return false;
393
    }
394
395
    /**
396
     * @param \Kunstmaan\NodeBundle\Entity\Node $node
397
     * @param bool                              $includeHiddenFromNav
398
     *
399
     * @return bool|\Kunstmaan\NodeBundle\Helper\NodeMenuItem
400
     */
401
    public function getNextSibling(Node $node, $includeHiddenFromNav = true)
402
    {
403
        $this->init();
404
405
        if (false !== $parent = $this->getParent($node)) {
406
            $siblings = $this->getChildren($parent, $includeHiddenFromNav);
407
408
            foreach ($siblings as $index => $child) {
409
                if ($child->getNode() === $node && (($index + 1) < \count(
410
                            $siblings
411
                        ))
412
                ) {
413
                    return $siblings[$index + 1];
414
                }
415
            }
416
        }
417
418
        return false;
419
    }
420
421
    /**
422
     * @param Node $node
423
     *
424
     * @return NodeMenuItem
425
     */
426
    public function getParent(Node $node)
427
    {
428
        $this->init();
429
        if ($node->getParent() && \array_key_exists(
430
                $node->getParent()->getId(),
431
                $this->allNodes
432
            )
433
        ) {
434
            return $this->allNodes[$node->getParent()->getId()];
435
        }
436
437
        return false;
438
    }
439
440
    /**
441
     * @param NodeTranslation $parentNode The parent node
442
     * @param string          $slug       The slug
443
     *
444
     * @return NodeTranslation
445
     */
446
    public function getNodeBySlug(NodeTranslation $parentNode, $slug)
447
    {
448
        return $this->em->getRepository('KunstmaanNodeBundle:NodeTranslation')
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Doctrine\Persistence\ObjectRepository as the method getNodeTranslationForSlug() does only exist in the following implementations of said interface: Kunstmaan\NodeBundle\Rep...deTranslationRepository.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

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