Completed
Pull Request — develop (#161)
by Wachter
22:54 queued 09:56
created

PageTreeRouteSubscriber::handleMove()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.016

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 15
ccs 9
cts 10
cp 0.9
rs 9.2
cc 4
eloc 9
nc 4
nop 1
crap 4.016
1
<?php
2
3
/*
4
 * This file is part of Sulu.
5
 *
6
 * (c) MASSIVE ART WebServices GmbH
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Sulu\Bundle\ArticleBundle\Document\Subscriber;
13
14
use PHPCR\NodeInterface;
15
use PHPCR\SessionInterface;
16
use Sulu\Bundle\ArticleBundle\Content\PageTreeRouteContentType;
17
use Sulu\Bundle\ArticleBundle\Document\ArticleDocument;
18
use Sulu\Bundle\ArticleBundle\Document\ArticleInterface;
19
use Sulu\Bundle\ContentBundle\Document\PageDocument;
20
use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
21
use Sulu\Bundle\DocumentManagerBundle\Bridge\PropertyEncoder;
22
use Sulu\Component\Content\Document\WorkflowStage;
23
use Sulu\Component\Content\Metadata\Factory\StructureMetadataFactoryInterface;
24
use Sulu\Component\Content\Metadata\PropertyMetadata;
25
use Sulu\Component\Content\Metadata\StructureMetadata;
26
use Sulu\Component\DocumentManager\Behavior\Mapping\PathBehavior;
27
use Sulu\Component\DocumentManager\DocumentManagerInterface;
28
use Sulu\Component\DocumentManager\Event\AbstractMappingEvent;
29
use Sulu\Component\DocumentManager\Event\MoveEvent;
30
use Sulu\Component\DocumentManager\Events;
31
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
32
33
/**
34
 * Handles relation between articles and pages.
35
 */
36
class PageTreeRouteSubscriber implements EventSubscriberInterface
37
{
38
    const ROUTE_PROPERTY = 'routePath';
39
    const TAG_NAME = 'sulu_article.article_route';
40
41
    /**
42
     * @var DocumentManagerInterface
43
     */
44
    protected $documentManager;
45
46
    /**
47
     * @var PropertyEncoder
48
     */
49
    protected $properyEncoder;
50
51
    /**
52
     * @var DocumentInspector
53
     */
54
    protected $documentInspector;
55
56
    /**
57
     * @var StructureMetadataFactoryInterface
58
     */
59
    protected $metadataFactory;
60
61
    /**
62
     * @var SessionInterface
63
     */
64
    protected $liveSession;
65
66
    /**
67
     * @param DocumentManagerInterface $documentManager
68
     * @param PropertyEncoder $properyEncoder
69
     * @param DocumentInspector $documentInspector
70
     * @param StructureMetadataFactoryInterface $metadataFactory
71
     * @param SessionInterface $liveSession
72
     */
73 16
    public function __construct(
74
        DocumentManagerInterface $documentManager,
75
        PropertyEncoder $properyEncoder,
76
        DocumentInspector $documentInspector,
77
        StructureMetadataFactoryInterface $metadataFactory,
78
        SessionInterface $liveSession
79
    ) {
80 16
        $this->documentManager = $documentManager;
81 16
        $this->properyEncoder = $properyEncoder;
82 16
        $this->documentInspector = $documentInspector;
83 16
        $this->metadataFactory = $metadataFactory;
84 16
        $this->liveSession = $liveSession;
85 16
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90 45
    public static function getSubscribedEvents()
91
    {
92
        return [
93
            // should be called before the live resource-segment will be updated
94 45
            Events::PUBLISH => ['handlePublish', 10],
95
            // should be called after the live resource-segment was be updated
96 45
            Events::MOVE => ['handleMove', -1000],
97
        ];
98
    }
99
100
    /**
101
     * Update route-paths of articles which are linked to the given page-document.
102
     *
103
     * @param AbstractMappingEvent $event
104
     */
105 16
    public function handlePublish(AbstractMappingEvent $event)
106
    {
107 16
        $document = $event->getDocument();
108 16
        if (!$document instanceof PageDocument || !$this->hasChangedResourceSegment($document)) {
109 16
            return;
110
        }
111
112 5
        $locale = $event->getLocale();
113 5
        $articles = $this->findLinkedArticles($document->getUuid(), $locale);
114 5
        foreach ($articles as $article) {
115 1
            $this->updateArticle($article, $document->getResourceSegment(), $locale);
0 ignored issues
show
Compatibility introduced by
$article of type object<Sulu\Bundle\Artic...ument\ArticleInterface> is not a sub-type of object<Sulu\Bundle\Artic...cument\ArticleDocument>. It seems like you assume a concrete implementation of the interface Sulu\Bundle\ArticleBundl...cument\ArticleInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
116
        }
117 5
    }
118
119
    /**
120
     * Update route-paths of articles which are linked to the given page-document.
121
     *
122
     * @param MoveEvent $event
123
     */
124 1
    public function handleMove(MoveEvent $event)
125
    {
126 1
        $document = $event->getDocument();
127 1
        if (!$document instanceof PageDocument) {
128
            return;
129
        }
130
131 1
        foreach ($this->documentInspector->getLocales($document) as $locale) {
132 1
            $localizedDocument = $this->documentManager->find($document->getUuid(), $locale);
133 1
            $articles = $this->findLinkedArticles($localizedDocument->getUuid(), $locale);
134 1
            foreach ($articles as $article) {
135 1
                $this->updateArticle($article, $localizedDocument->getResourceSegment(), $locale);
0 ignored issues
show
Compatibility introduced by
$article of type object<Sulu\Bundle\Artic...ument\ArticleInterface> is not a sub-type of object<Sulu\Bundle\Artic...cument\ArticleDocument>. It seems like you assume a concrete implementation of the interface Sulu\Bundle\ArticleBundl...cument\ArticleInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
136
            }
137
        }
138 1
    }
139
140
    /**
141
     * Find articles linked to the given page.
142
     *
143
     * @param string $uuid
144
     * @param string $locale
145
     *
146
     * @return ArticleInterface[]
147
     */
148 5
    private function findLinkedArticles($uuid, $locale)
149
    {
150 5
        $where = [];
151 5
        foreach ($this->metadataFactory->getStructures('article') as $metadata) {
152 5
            $property = $this->getRoutePathPropertyName($metadata);
153 5
            if (null === $property || PageTreeRouteContentType::NAME !== $property->getType()) {
154 5
                continue;
155
            }
156
157 5
            $where[] = sprintf(
158 5
                '([%s] = "%s" AND [%s-page] = "%s")',
159 5
                $this->properyEncoder->localizedSystemName('template', $locale),
160 5
                $metadata->getName(),
161 5
                $this->properyEncoder->localizedSystemName($property->getName(), $locale),
162
                $uuid
163
            );
164
        }
165
166 5
        $query = $this->documentManager->createQuery(
167
            sprintf(
168 5
                'SELECT * FROM [nt:unstructured] WHERE [jcr:mixinTypes] = "sulu:article" AND %s',
169 5
                implode(' AND ', $where)
170
            ),
171
            $locale
172
        );
173
174 5
        return $query->execute();
175
    }
176
177
    /**
178
     * Update route of given article.
179
     *
180
     * @param ArticleDocument $article
181
     * @param string $resourceSegment
182
     * @param string $locale
183
     */
184 2
    private function updateArticle(ArticleDocument $article, $resourceSegment, $locale)
185
    {
186 2
        $property = $this->getRoutePathPropertyNameByStructureType($article->getStructureType());
187 2
        $propertyName = $this->properyEncoder->localizedContentName($property->getName(), $locale);
188
189 2
        $node = $this->documentInspector->getNode($article);
190 2
        $node->setProperty($propertyName . '-page-path', $resourceSegment);
191
192 2
        $suffix = $node->getPropertyValueWithDefault($propertyName . '-suffix', null);
193 2
        if ($suffix) {
194 2
            $path = rtrim($resourceSegment, '/') . '/' . $suffix;
195 2
            $node->setProperty($propertyName, $path);
196 2
            $article->setRoutePath($path);
197
        }
198
199 2
        if (WorkflowStage::PUBLISHED === $article->getWorkflowStage()) {
200
            $this->documentManager->publish($article, $locale);
201
        }
202 2
    }
203
204
    /**
205
     * Returns true if the resource-segment was changed in the draft page.
206
     *
207
     * @param PageDocument $document
208
     *
209
     * @return bool
210
     */
211 5
    private function hasChangedResourceSegment(PageDocument $document)
212
    {
213 5
        $metadata = $this->metadataFactory->getStructureMetadata('page', $document->getStructureType());
214
215 5
        $urlProperty = $metadata->getPropertyByTagName('sulu.rlp');
216 5
        $urlPropertyName = $this->properyEncoder->localizedContentName($urlProperty->getName(), $document->getLocale());
217
218 5
        $liveNode = $this->getLiveNode($document);
219
220 5
        return $liveNode->getPropertyValueWithDefault($urlPropertyName, null) !== $document->getResourceSegment();
221
    }
222
223
    /**
224
     * Returns "routePath" property.
225
     *
226
     * @param string $structureType
227
     *
228
     * @return PropertyMetadata
229
     */
230 2 View Code Duplication
    private function getRoutePathPropertyNameByStructureType($structureType)
0 ignored issues
show
Duplication introduced by
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...
231
    {
232 2
        $metadata = $this->metadataFactory->getStructureMetadata('article', $structureType);
233 2
        if ($metadata->hasTag(self::TAG_NAME)) {
234
            return $metadata->getPropertyByTagName(self::TAG_NAME);
235
        }
236
237 2
        return $metadata->getProperty(self::ROUTE_PROPERTY);
238
    }
239
240
    /**
241
     * Returns "routePath" property.
242
     *
243
     * @param StructureMetadata $metadata
244
     *
245
     * @return PropertyMetadata
246
     */
247 5
    private function getRoutePathPropertyName(StructureMetadata $metadata)
248
    {
249 5
        if ($metadata->hasTag(self::TAG_NAME)) {
250
            return $metadata->getPropertyByTagName(self::TAG_NAME);
251
        }
252
253 5
        if (!$metadata->hasProperty(self::ROUTE_PROPERTY)) {
254 5
            return;
255
        }
256
257 5
        return $metadata->getProperty(self::ROUTE_PROPERTY);
258
    }
259
260
    /**
261
     * Returns the live node for given document.
262
     *
263
     * @param PathBehavior $document
264
     *
265
     * @return NodeInterface
266
     */
267 5
    private function getLiveNode(PathBehavior $document)
268
    {
269 5
        return $this->liveSession->getNode($document->getPath());
270
    }
271
}
272