Completed
Push — master ( 1de9b7...830752 )
by Kristof
38:46 queued 24:09
created

src/Kunstmaan/NodeBundle/Helper/NodeMenu.php (12 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
0 ignored issues
show
Should the type for parameter $currentNode not be null|Node?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
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;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array<integer,object<Kun...e\Helper\NodeMenuItem>> of property $breadCrumb.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
175
        $this->childNodes = array();
176
        $this->topNodeMenuItems = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array<integer,object<Kun...e\Helper\NodeMenuItem>> of property $topNodeMenuItems.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
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,
0 ignored issues
show
false is of type boolean, but the function expects a integer|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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) {
0 ignored issues
show
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...
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) {
0 ignored issues
show
The expression $nodes of type object<Kunstmaan\NodeBundle\Entity\Node> is not traversable.
Loading history...
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...
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) {
0 ignored issues
show
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...
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
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
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
0 ignored issues
show
Should the type for parameter $parent not be NodeTranslation|NodeMenuItem|HasNodeInterface|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
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,
0 ignored issues
show
It seems like $nodeTranslation defined by $rootNode->getNodeTransl... $this->includeOffline) on line 565 can be null; however, Kunstmaan\NodeBundle\Hel...MenuItem::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
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) {
0 ignored issues
show
The expression $topNodes of type object<Kunstmaan\NodeBundle\Entity\Node> is not traversable.
Loading history...
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...
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