Completed
Pull Request — develop (#161)
by Wachter
15:19
created

hasChangedResourceSegment()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 11
rs 9.4285
cc 1
eloc 6
nc 1
nop 1
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
    public function __construct(
74
        DocumentManagerInterface $documentManager,
75
        PropertyEncoder $properyEncoder,
76
        DocumentInspector $documentInspector,
77
        StructureMetadataFactoryInterface $metadataFactory,
78
        SessionInterface $liveSession
79
    ) {
80
        $this->documentManager = $documentManager;
81
        $this->properyEncoder = $properyEncoder;
82
        $this->documentInspector = $documentInspector;
83
        $this->metadataFactory = $metadataFactory;
84
        $this->liveSession = $liveSession;
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90
    public static function getSubscribedEvents()
91
    {
92
        return [
93
            // should be called before the live resource-segment will be updated
94
            Events::PUBLISH => ['handlePublish', 10],
95
            // should be called after the live resource-segment was be updated
96
            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
    public function handlePublish(AbstractMappingEvent $event)
106
    {
107
        $document = $event->getDocument();
108
        if (!$document instanceof PageDocument || !$this->hasChangedResourceSegment($document)) {
109
            return;
110
        }
111
112
        $locale = $event->getLocale();
113
        $articles = $this->findLinkedArticles($document->getUuid(), $locale);
114
        foreach ($articles as $article) {
115
            $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
    }
118
119
    /**
120
     * Update route-paths of articles which are linked to the given page-document.
121
     *
122
     * @param MoveEvent $event
123
     */
124
    public function handleMove(MoveEvent $event)
125
    {
126
        $document = $event->getDocument();
127
        if (!$document instanceof PageDocument) {
128
            return;
129
        }
130
131
        foreach ($this->documentInspector->getLocales($document) as $locale) {
132
            $localizedDocument = $this->documentManager->find($document->getUuid(), $locale);
133
            $articles = $this->findLinkedArticles($localizedDocument->getUuid(), $locale);
134
            foreach ($articles as $article) {
135
                $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
    }
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
    private function findLinkedArticles($uuid, $locale)
149
    {
150
        $where = [];
151
        foreach ($this->metadataFactory->getStructures('article') as $metadata) {
152
            $property = $this->getRoutePathPropertyName($metadata);
153
            if (null === $property || PageTreeRouteContentType::NAME !== $property->getType()) {
154
                continue;
155
            }
156
157
            $where[] = sprintf(
158
                '([%s] = "%s" AND [%s-page] = "%s")',
159
                $this->properyEncoder->localizedSystemName('template', $locale),
160
                $metadata->getName(),
161
                $this->properyEncoder->localizedSystemName($property->getName(), $locale),
162
                $uuid
163
            );
164
        }
165
166
        $query = $this->documentManager->createQuery(
167
            sprintf(
168
                'SELECT * FROM [nt:unstructured] WHERE [jcr:mixinTypes] = "sulu:article" AND %s',
169
                implode(' AND ', $where)
170
            ),
171
            $locale
172
        );
173
174
        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
    private function updateArticle(ArticleDocument $article, $resourceSegment, $locale)
185
    {
186
        $property = $this->getRoutePathPropertyNameByStructureType($article->getStructureType());
187
        $propertyName = $this->properyEncoder->localizedContentName($property->getName(), $locale);
188
189
        $node = $this->documentInspector->getNode($article);
190
        $node->setProperty($propertyName . '-page-path', $resourceSegment);
191
192
        $suffix = $node->getPropertyValueWithDefault($propertyName . '-suffix', null);
193
        if ($suffix) {
194
            $path = rtrim($resourceSegment, '/') . '/' . $suffix;
195
            $node->setProperty($propertyName, $path);
196
            $article->setRoutePath($path);
197
        }
198
199
        if (WorkflowStage::PUBLISHED === $article->getWorkflowStage()) {
200
            $this->documentManager->publish($article, $locale);
201
        }
202
    }
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
    private function hasChangedResourceSegment(PageDocument $document)
212
    {
213
        $metadata = $this->metadataFactory->getStructureMetadata('page', $document->getStructureType());
214
215
        $urlProperty = $metadata->getPropertyByTagName('sulu.rlp');
216
        $urlPropertyName = $this->properyEncoder->localizedContentName($urlProperty->getName(), $document->getLocale());
217
218
        $liveNode = $this->getLiveNode($document);
219
220
        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 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
        $metadata = $this->metadataFactory->getStructureMetadata('article', $structureType);
233
        if ($metadata->hasTag(self::TAG_NAME)) {
234
            return $metadata->getPropertyByTagName(self::TAG_NAME);
235
        }
236
237
        return $metadata->getProperty(self::ROUTE_PROPERTY);
238
    }
239
240
    /**
241
     * Returns "routePath" property.
242
     *
243
     * @param StructureMetadata $metadata
244
     *
245
     * @return PropertyMetadata
246
     */
247
    private function getRoutePathPropertyName(StructureMetadata $metadata)
248
    {
249
        if ($metadata->hasTag(self::TAG_NAME)) {
250
            return $metadata->getPropertyByTagName(self::TAG_NAME);
251
        }
252
253
        if (!$metadata->hasProperty(self::ROUTE_PROPERTY)) {
254
            return;
255
        }
256
257
        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
    private function getLiveNode(PathBehavior $document)
268
    {
269
        return $this->liveSession->getNode($document->getPath());
270
    }
271
}
272