Completed
Push — master ( 06c1ce...67d37c )
by Jeroen
06:20
created

Kunstmaan/NodeBundle/Helper/PageCloningHelper.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\Entity\BaseUser;
7
use Kunstmaan\AdminBundle\Helper\CloneHelper;
8
use Kunstmaan\AdminBundle\Helper\Security\Acl\Permission\PermissionMap;
9
use Kunstmaan\NodeBundle\Entity\DuplicateSubPageInterface;
10
use Kunstmaan\NodeBundle\Entity\HasNodeInterface;
11
use Kunstmaan\NodeBundle\Entity\Node;
12
use Kunstmaan\NodeBundle\Entity\PageInterface;
13
use Kunstmaan\NodeBundle\Event\Events;
14
use Kunstmaan\NodeBundle\Event\NodeDuplicateEvent;
15
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
16
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
17
use Symfony\Component\Security\Acl\Model\AclProviderInterface;
18
use Symfony\Component\Security\Acl\Model\EntryInterface;
19
use Symfony\Component\Security\Acl\Model\ObjectIdentityRetrievalStrategyInterface;
20
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
21
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
22
23
class PageCloningHelper
24
{
25
    /** @var EntityManagerInterface */
26
    private $em;
27
28
    /** @var CloneHelper */
29
    private $cloneHelper;
30
31
    /** @var AclProviderInterface */
32
    private $aclProvider;
33
34
    /** @var ObjectIdentityRetrievalStrategyInterface */
35
    private $identityRetrievalStrategy;
36
37
    /** @var AuthorizationCheckerInterface */
38
    private $authorizationCheckerInterface;
39
40
    /** @var EventDispatcherInterface */
41
    private $eventDispatcherInterface;
42
43
    public function __construct(EntityManagerInterface $em, CloneHelper $cloneHelper, AclProviderInterface $aclProvider, ObjectIdentityRetrievalStrategyInterface $identityRetrivalStrategy, AuthorizationCheckerInterface $authorizationChecker, EventDispatcherInterface $eventDispatcher)
44
    {
45
        $this->em = $em;
46
        $this->cloneHelper = $cloneHelper;
47
        $this->aclProvider = $aclProvider;
48
        $this->identityRetrievalStrategy = $identityRetrivalStrategy;
49
        $this->authorizationCheckerInterface = $authorizationChecker;
50
        $this->eventDispatcherInterface = $eventDispatcher;
51
    }
52
53
    /**
54
     * @throws AccessDeniedException
55
     */
56
    public function duplicateWithChildren($id, string $locale, BaseUser $user, string $title = null): Node
57
    {
58
        /* @var Node $parentNode */
59
        $originalNode = $this->em->getRepository('KunstmaanNodeBundle:Node')->find($id);
60
61
        $this->denyAccessUnlessGranted(PermissionMap::PERMISSION_EDIT, $originalNode);
62
63
        $this->eventDispatcherInterface->dispatch(Events::PRE_DUPLICATE_WITH_CHILDREN, new NodeDuplicateEvent($originalNode));
64
65
        $newPage = $this->clonePage($originalNode, $locale, $title);
66
        $nodeNewPage = $this->createNodeStructureForNewPage($originalNode, $newPage, $user, $locale);
67
68
        $this->cloneChildren($originalNode, $newPage, $user, $locale);
69
70
        $this->eventDispatcherInterface->dispatch(Events::POST_DUPLICATE_WITH_CHILDREN, new NodeDuplicateEvent($originalNode));
71
72
        return $nodeNewPage;
73
    }
74
75
    private function denyAccessUnlessGranted($attributes, $subject = null, $message = 'Access Denied.')
76
    {
77
        if (!$this->authorizationCheckerInterface->isGranted($attributes, $subject)) {
78
            $exception = new AccessDeniedException();
79
            $exception->setAttributes($attributes);
80
            $exception->setSubject($subject);
81
82
            throw $exception;
83
        }
84
    }
85
86
    private function clonePage(Node $originalNode, $locale, $title = null)
87
    {
88
        $originalNodeTranslations = $originalNode->getNodeTranslation($locale, true);
89
        $originalRef = $originalNodeTranslations->getPublicNodeVersion()->getRef($this->em);
90
91
        $newPage = $this->cloneHelper->deepCloneAndSave($originalRef);
92
93
        if ($title !== null) {
94
            $newPage->setTitle($title);
95
        }
96
97
        //set the parent
98
        $parentNodeTranslation = $originalNode->getParent()->getNodeTranslation($locale, true);
99
        $parent = $parentNodeTranslation->getPublicNodeVersion()->getRef($this->em);
100
        $newPage->setParent($parent);
101
102
        $this->em->persist($newPage);
103
        $this->em->flush();
104
105
        return $newPage;
106
    }
107
108
    private function createNodeStructureForNewPage(Node $originalNode, HasNodeInterface $newPage, BaseUser $user, string $locale): Node
109
    {
110
        /* @var Node $nodeNewPage */
111
        $nodeNewPage = $this->em->getRepository('KunstmaanNodeBundle:Node')->createNodeFor($newPage, $locale, $user);
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Doctrine\Persistence\ObjectRepository as the method createNodeFor() does only exist in the following implementations of said interface: Kunstmaan\NodeBundle\Repository\NodeRepository.

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...
112
113
        if ($newPage->isStructureNode()) {
114
            $nodeTranslation = $nodeNewPage->getNodeTranslation($locale, true);
115
            $nodeTranslation->setSlug('');
116
            $this->em->persist($nodeTranslation);
117
        }
118
        $this->em->flush();
119
120
        $this->updateAcl($originalNode, $nodeNewPage);
121
122
        return $nodeNewPage;
123
    }
124
125 View Code Duplication
    private function updateAcl($originalNode, $nodeNewPage): void
126
    {
127
        $originalIdentity = $this->identityRetrievalStrategy->getObjectIdentity($originalNode);
128
        $originalAcl = $this->aclProvider->findAcl($originalIdentity);
129
130
        $newIdentity = $this->identityRetrievalStrategy->getObjectIdentity($nodeNewPage);
131
        $newAcl = $this->aclProvider->createAcl($newIdentity);
132
133
        $aces = $originalAcl->getObjectAces();
134
        /* @var EntryInterface $ace */
135
        foreach ($aces as $ace) {
136
            $securityIdentity = $ace->getSecurityIdentity();
137
            if ($securityIdentity instanceof RoleSecurityIdentity) {
138
                $newAcl->insertObjectAce($securityIdentity, $ace->getMask());
139
            }
140
        }
141
        $this->aclProvider->updateAcl($newAcl);
142
    }
143
144
    private function cloneChildren(Node $originalNode, PageInterface $newPage, BaseUser $user, string $locale): void
145
    {
146
        $nodeChildren = $originalNode->getChildren();
147
        foreach ($nodeChildren as $originalNodeChild) {
148
            $originalNodeTranslations = $originalNodeChild->getNodeTranslation($locale, true);
149
            $originalRef = $originalNodeTranslations->getPublicNodeVersion()->getRef($this->em);
150
151
            if (!$originalRef instanceof DuplicateSubPageInterface || !$originalRef->skipClone()) {
152
                $newChildPage = $this->clonePage($originalNodeChild, $locale);
153
                $newChildPage->setParent($newPage);
154
155
                $this->createNodeStructureForNewPage($originalNodeChild, $newChildPage, $user, $locale);
156
                $this->cloneChildren($originalNodeChild, $newChildPage, $user, $locale);
157
            }
158
        }
159
    }
160
}
161