Completed
Push — master ( 1dbefb...c77374 )
by Jeroen
32s queued 11s
created

NodeAdminPublisher::dispatch()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 10
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 10
loc 10
ccs 0
cts 5
cp 0
c 0
b 0
f 0
rs 9.9332
cc 2
nc 2
nop 2
crap 6
1
<?php
2
3
namespace Kunstmaan\NodeBundle\Helper\NodeAdmin;
4
5
use Doctrine\ORM\EntityManager;
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\HasNodeInterface;
10
use Kunstmaan\NodeBundle\Entity\Node;
11
use Kunstmaan\NodeBundle\Entity\NodeTranslation;
12
use Kunstmaan\NodeBundle\Entity\NodeVersion;
13
use Kunstmaan\NodeBundle\Entity\QueuedNodeTranslationAction;
14
use Kunstmaan\NodeBundle\Event\Events;
15
use Kunstmaan\NodeBundle\Event\NodeEvent;
16
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
17
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\HttpFoundation\Session\Session;
20
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
21
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
22
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
23
use Symfony\Component\Translation\TranslatorInterface;
24
use Kunstmaan\AdminBundle\FlashMessages\FlashTypes;
25
26
class NodeAdminPublisher
27
{
28
    /**
29
     * @var EntityManager
30
     */
31
    private $em;
32
33
    /**
34
     * @var TokenStorageInterface
35
     */
36
    private $tokenStorage;
37
38
    /**
39
     * @var AuthorizationCheckerInterface
40
     */
41
    private $authorizationChecker;
42
43
    /**
44
     * @var EventDispatcherInterface
45
     */
46
    private $eventDispatcher;
47
48
    /**
49
     * @var CloneHelper
50
     */
51
    private $cloneHelper;
52
53
    /**
54
     * @param EntityManager                 $em                   The entity manager
55
     * @param TokenStorageInterface         $tokenStorage         The security token storage
56
     * @param AuthorizationCheckerInterface $authorizationChecker The security authorization checker
57
     * @param EventDispatcherInterface      $eventDispatcher      The Event dispatcher
58
     * @param CloneHelper                   $cloneHelper          The clone helper
59
     */
60 View Code Duplication
    public function __construct(
0 ignored issues
show
Duplication introduced by Wim Vandersmissen
This method seems to be duplicated in 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...
61
        EntityManager $em,
0 ignored issues
show
Bug introduced by Wim Vandersmissen
You have injected the EntityManager via parameter $em. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
62
        TokenStorageInterface $tokenStorage,
63
        AuthorizationCheckerInterface $authorizationChecker,
64
        EventDispatcherInterface $eventDispatcher,
65
        CloneHelper $cloneHelper
66
    ) {
67
        $this->em = $em;
68
        $this->tokenStorage = $tokenStorage;
69
        $this->authorizationChecker = $authorizationChecker;
70
        $this->eventDispatcher = $eventDispatcher;
71
        $this->cloneHelper = $cloneHelper;
72
    }
73
74
    /**
75
     * If there is a draft version it'll try to publish the draft first. Makse snese because if you want to publish the public version you don't publish but you save.
76
     *
77
     * @param NodeTranslation $nodeTranslation
78
     * @param BaseUser|null   $user
79
     *
80
     *  @throws AccessDeniedException
81
     */
82
    public function publish(NodeTranslation $nodeTranslation, $user = null)
83
    {
84
        if (false === $this->authorizationChecker->isGranted(PermissionMap::PERMISSION_PUBLISH, $nodeTranslation->getNode())) {
85
            throw new AccessDeniedException();
86
        }
87
88
        if (\is_null($user)) {
89
            $user = $this->tokenStorage->getToken()->getUser();
90
        }
91
92
        $node = $nodeTranslation->getNode();
93
94
        $nodeVersion = $nodeTranslation->getNodeVersion('draft');
95
        if (!\is_null($nodeVersion)) {
96
            $page = $nodeVersion->getRef($this->em);
97
            /** @var NodeVersion $nodeVersion */
98
            $nodeVersion = $this->createPublicVersion($page, $nodeTranslation, $nodeVersion, $user);
0 ignored issues
show
Bug introduced by Wim Vandersmissen
It seems like $page defined by $nodeVersion->getRef($this->em) on line 96 can be null; however, Kunstmaan\NodeBundle\Hel...::createPublicVersion() 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...
99
            $nodeTranslation = $nodeVersion->getNodeTranslation();
100
        } else {
101
            $nodeVersion = $nodeTranslation->getPublicNodeVersion();
102
        }
103
104
        $page = $nodeVersion->getRef($this->em);
105
106
        $this->dispatch(
107
            new NodeEvent($node, $nodeTranslation, $nodeVersion, $page),
0 ignored issues
show
Bug introduced by Wim Vandersmissen
It seems like $page defined by $nodeVersion->getRef($this->em) on line 104 can be null; however, Kunstmaan\NodeBundle\Eve...odeEvent::__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...
108
            Events::PRE_PUBLISH
109
        );
110
        $nodeTranslation
111
            ->setOnline(true)
112
            ->setPublicNodeVersion($nodeVersion)
113
            ->setUpdated(new \DateTime());
114
        $this->em->persist($nodeTranslation);
115
        $this->em->flush();
116
117
        // Remove scheduled task
118
        $this->unSchedulePublish($nodeTranslation);
119
120
        $this->dispatch(
121
            new NodeEvent($node, $nodeTranslation, $nodeVersion, $page),
0 ignored issues
show
Bug introduced by Wim Vandersmissen
It seems like $page defined by $nodeVersion->getRef($this->em) on line 104 can be null; however, Kunstmaan\NodeBundle\Eve...odeEvent::__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...
122
            Events::POST_PUBLISH
123
        );
124
    }
125
126
    /**
127
     * @param NodeTranslation $nodeTranslation The NodeTranslation
128
     * @param \DateTime       $date            The date to publish
129
     *
130
     * @throws AccessDeniedException
131
     */
132 View Code Duplication
    public function publishLater(NodeTranslation $nodeTranslation, \DateTime $date)
0 ignored issues
show
Duplication introduced by Kris Pypen
This method seems to be duplicated in 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...
133
    {
134
        $node = $nodeTranslation->getNode();
135
        if (false === $this->authorizationChecker->isGranted(PermissionMap::PERMISSION_PUBLISH, $node)) {
136
            throw new AccessDeniedException();
137
        }
138
139
        //remove existing first
140
        $this->unSchedulePublish($nodeTranslation);
141
142
        $user = $this->tokenStorage->getToken()->getUser();
143
        $queuedNodeTranslationAction = new QueuedNodeTranslationAction();
144
        $queuedNodeTranslationAction
145
            ->setNodeTranslation($nodeTranslation)
146
            ->setAction(QueuedNodeTranslationAction::ACTION_PUBLISH)
147
            ->setUser($user)
148
            ->setDate($date);
149
        $this->em->persist($queuedNodeTranslationAction);
150
        $this->em->flush();
151
    }
152
153
    /**
154
     * @param NodeTranslation $nodeTranslation
155
     *
156
     * @throws AccessDeniedException
157
     */
158
    public function unPublish(NodeTranslation $nodeTranslation)
159
    {
160
        if (false === $this->authorizationChecker->isGranted(PermissionMap::PERMISSION_UNPUBLISH, $nodeTranslation->getNode())) {
161
            throw new AccessDeniedException();
162
        }
163
164
        $node = $nodeTranslation->getNode();
165
        $nodeVersion = $nodeTranslation->getPublicNodeVersion();
166
        $page = $nodeVersion->getRef($this->em);
167
168
        $this->dispatch(
169
            new NodeEvent($node, $nodeTranslation, $nodeVersion, $page),
0 ignored issues
show
Bug introduced by Wim Vandersmissen
It seems like $page defined by $nodeVersion->getRef($this->em) on line 166 can be null; however, Kunstmaan\NodeBundle\Eve...odeEvent::__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...
170
            Events::PRE_UNPUBLISH
171
        );
172
        $nodeTranslation->setOnline(false);
173
        $this->em->persist($nodeTranslation);
174
        $this->em->flush();
175
176
        // Remove scheduled task
177
        $this->unSchedulePublish($nodeTranslation);
178
179
        $this->dispatch(
180
            new NodeEvent($node, $nodeTranslation, $nodeVersion, $page),
0 ignored issues
show
Bug introduced by Wim Vandersmissen
It seems like $page defined by $nodeVersion->getRef($this->em) on line 166 can be null; however, Kunstmaan\NodeBundle\Eve...odeEvent::__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...
181
            Events::POST_UNPUBLISH
182
        );
183
    }
184
185
    /**
186
     * @param NodeTranslation $nodeTranslation The NodeTranslation
187
     * @param \DateTime       $date            The date to unpublish
188
     *
189
     * @throws AccessDeniedException
190
     */
191 View Code Duplication
    public function unPublishLater(NodeTranslation $nodeTranslation, \DateTime $date)
0 ignored issues
show
Duplication introduced by Kris Pypen
This method seems to be duplicated in 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...
192
    {
193
        $node = $nodeTranslation->getNode();
194
        if (false === $this->authorizationChecker->isGranted(PermissionMap::PERMISSION_UNPUBLISH, $node)) {
195
            throw new AccessDeniedException();
196
        }
197
198
        //remove existing first
199
        $this->unSchedulePublish($nodeTranslation);
200
        $user = $this->tokenStorage->getToken()->getUser();
201
        $queuedNodeTranslationAction = new QueuedNodeTranslationAction();
202
        $queuedNodeTranslationAction
203
            ->setNodeTranslation($nodeTranslation)
204
            ->setAction(QueuedNodeTranslationAction::ACTION_UNPUBLISH)
205
            ->setUser($user)
206
            ->setDate($date);
207
        $this->em->persist($queuedNodeTranslationAction);
208
        $this->em->flush();
209
    }
210
211
    /**
212
     * @param NodeTranslation $nodeTranslation
213
     */
214
    public function unSchedulePublish(NodeTranslation $nodeTranslation)
215
    {
216
        /* @var Node $node */
217
        $queuedNodeTranslationAction = $this->em->getRepository(QueuedNodeTranslationAction::class)
218
            ->findOneBy(array('nodeTranslation' => $nodeTranslation));
219
220
        if (!\is_null($queuedNodeTranslationAction)) {
221
            $this->em->remove($queuedNodeTranslationAction);
222
            $this->em->flush();
223
        }
224
    }
225
226
    /**
227
     * This shouldn't be here either but it's an improvement.
228
     *
229
     * @param HasNodeInterface $page            The page
230
     * @param NodeTranslation  $nodeTranslation The node translation
231
     * @param NodeVersion      $nodeVersion     The node version
232
     * @param BaseUser         $user            The user
233
     *
234
     * @return mixed
235
     */
236
    public function createPublicVersion(
237
        HasNodeInterface $page,
238
        NodeTranslation $nodeTranslation,
239
        NodeVersion $nodeVersion,
240
        BaseUser $user
241
    ) {
242
        $newPublicPage = $this->cloneHelper->deepCloneAndSave($page);
243
        $newNodeVersion = $this->em->getRepository(NodeVersion::class)->createNodeVersionFor(
0 ignored issues
show
Bug introduced by Jeroen Thora
It seems like you code against a concrete implementation and not the interface Doctrine\Persistence\ObjectRepository as the method createNodeVersionFor() does only exist in the following implementations of said interface: Kunstmaan\NodeBundle\Rep...y\NodeVersionRepository.

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...
244
            $newPublicPage,
245
            $nodeTranslation,
246
            $user,
247
            $nodeVersion
248
        );
249
250
        $newNodeVersion
251
            ->setOwner($nodeVersion->getOwner())
252
            ->setUpdated($nodeVersion->getUpdated())
253
            ->setCreated($nodeVersion->getCreated());
254
255
        $nodeVersion
256
            ->setOwner($user)
257
            ->setCreated(new \DateTime())
258
            ->setOrigin($newNodeVersion);
259
260
        $this->em->persist($newNodeVersion);
261
        $this->em->persist($nodeVersion);
262
        $this->em->persist($nodeTranslation);
263
        $this->em->flush();
264
        $this->dispatch(
265
            new NodeEvent($nodeTranslation->getNode(), $nodeTranslation, $nodeVersion, $newPublicPage),
266
            Events::CREATE_PUBLIC_VERSION
267
        );
268
269
        return $newNodeVersion;
270
    }
271
272
    /**
273
     * @param Request         $request
274
     * @param NodeTranslation $nodeTranslation
275
     */
276 View Code Duplication
    public function chooseHowToPublish(Request $request, NodeTranslation $nodeTranslation, TranslatorInterface $translator)
0 ignored issues
show
Duplication introduced by Peter Zentjens
This method seems to be duplicated in 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...
277
    {
278
        /** @var Session $session */
279
        $session = $request->getSession();
280
281
        if ($request->request->has('publish_later') && $request->get('pub_date')) {
282
            $date = new \DateTime(
283
                $request->get('pub_date') . ' ' . $request->get('pub_time')
284
            );
285
            $this->publishLater($nodeTranslation, $date);
286
            $session->getFlashBag()->add(
287
                FlashTypes::SUCCESS,
288
                $translator->trans('kuma_node.admin.publish.flash.success_scheduled')
289
            );
290
        } else {
291
            $this->publish($nodeTranslation);
292
            $session->getFlashBag()->add(
293
                FlashTypes::SUCCESS,
294
                $translator->trans('kuma_node.admin.publish.flash.success_published')
295
            );
296
        }
297
    }
298
299
    /**
300
     * @param Request         $request
301
     * @param NodeTranslation $nodeTranslation
302
     */
303 View Code Duplication
    public function chooseHowToUnpublish(Request $request, NodeTranslation $nodeTranslation, TranslatorInterface $translator)
0 ignored issues
show
Duplication introduced by Peter Zentjens
This method seems to be duplicated in 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...
304
    {
305
        /** @var Session $session */
306
        $session = $request->getSession();
307
308
        if ($request->request->has('unpublish_later') && $request->get('unpub_date')) {
309
            $date = new \DateTime($request->get('unpub_date') . ' ' . $request->get('unpub_time'));
310
            $this->unPublishLater($nodeTranslation, $date);
311
            $session->getFlashBag()->add(
312
                FlashTypes::SUCCESS,
313
                $translator->trans('kuma_node.admin.unpublish.flash.success_scheduled')
314
            );
315
        } else {
316
            $this->unPublish($nodeTranslation);
317
            $session->getFlashBag()->add(
318
                FlashTypes::SUCCESS,
319
                $translator->trans('kuma_node.admin.unpublish.flash.success_unpublished')
320
            );
321
        }
322
    }
323
324
    /**
325
     * @param object $event
326
     * @param string $eventName
327
     *
328
     * @return object
329
     */
330 View Code Duplication
    private function dispatch($event, string $eventName)
0 ignored issues
show
Duplication introduced by Jeroen Thora
This method seems to be duplicated in 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...
331
    {
332
        if (class_exists(LegacyEventDispatcherProxy::class)) {
333
            $eventDispatcher = LegacyEventDispatcherProxy::decorate($this->eventDispatcher);
334
335
            return $eventDispatcher->dispatch($event, $eventName);
336
        }
337
338
        return $this->eventDispatcher->dispatch($eventName, $event);
339
    }
340
}
341