Completed
Pull Request — develop (#305)
by Wachter
15:11
created

ArticleSubscriber::publishChildren()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 7
cts 7
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 1
crap 4
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\PathNotFoundException;
16
use Sulu\Bundle\ArticleBundle\Document\ArticleDocument;
17
use Sulu\Bundle\ArticleBundle\Document\ArticlePageDocument;
18
use Sulu\Bundle\ArticleBundle\Document\Index\IndexerInterface;
19
use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
20
use Sulu\Bundle\DocumentManagerBundle\Bridge\PropertyEncoder;
21
use Sulu\Component\Content\Document\LocalizationState;
22
use Sulu\Component\Content\Document\WorkflowStage;
23
use Sulu\Component\DocumentManager\DocumentManagerInterface;
24
use Sulu\Component\DocumentManager\Event\AbstractMappingEvent;
25
use Sulu\Component\DocumentManager\Event\CopyEvent;
26
use Sulu\Component\DocumentManager\Event\FlushEvent;
27
use Sulu\Component\DocumentManager\Event\HydrateEvent;
28
use Sulu\Component\DocumentManager\Event\MetadataLoadEvent;
29
use Sulu\Component\DocumentManager\Event\PersistEvent;
30
use Sulu\Component\DocumentManager\Event\PublishEvent;
31
use Sulu\Component\DocumentManager\Event\RemoveDraftEvent;
32
use Sulu\Component\DocumentManager\Event\RemoveEvent;
33
use Sulu\Component\DocumentManager\Event\ReorderEvent;
34
use Sulu\Component\DocumentManager\Event\UnpublishEvent;
35
use Sulu\Component\DocumentManager\Events;
36
use Sulu\Component\Util\SortUtils;
37
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
38
39
/**
40
 * Indexes article and generate route on persist and removes it from index and routing on delete.
41
 */
42
class ArticleSubscriber implements EventSubscriberInterface
43
{
44
    const PAGES_PROPERTY = 'suluPages';
45
46
    /**
47
     * @var IndexerInterface
48
     */
49
    private $indexer;
50
51
    /**
52
     * @var IndexerInterface
53
     */
54
    private $liveIndexer;
55
56
    /**
57
     * @var DocumentManagerInterface
58
     */
59
    private $documentManager;
60
61
    /**
62
     * @var DocumentInspector
63
     */
64
    private $documentInspector;
65
66
    /**
67
     * @var PropertyEncoder
68
     */
69
    private $propertyEncoder;
70
71
    /**
72
     * @var array
73
     */
74
    private $documents = [];
75
76
    /**
77
     * @var array
78
     */
79
    private $liveDocuments = [];
80
81
    /**
82
     * @param IndexerInterface $indexer
83
     * @param IndexerInterface $liveIndexer
84
     * @param DocumentManagerInterface $documentManager
85
     * @param DocumentInspector $documentInspector
86
     * @param PropertyEncoder $propertyEncoder
87
     */
88 70
    public function __construct(
89
        IndexerInterface $indexer,
90
        IndexerInterface $liveIndexer,
91
        DocumentManagerInterface $documentManager,
92
        DocumentInspector $documentInspector,
93
        PropertyEncoder $propertyEncoder
94
    ) {
95 70
        $this->indexer = $indexer;
96 70
        $this->liveIndexer = $liveIndexer;
97 70
        $this->documentManager = $documentManager;
98 70
        $this->documentInspector = $documentInspector;
99 70
        $this->propertyEncoder = $propertyEncoder;
100 70
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105 55
    public static function getSubscribedEvents()
106
    {
107
        return [
108 55
            Events::HYDRATE => [
109
                ['hydratePageData', -2000],
110
            ],
111 55
            Events::PERSIST => [
112
                ['handleUuid', 480],
113
                ['handleScheduleIndex', -500],
114
                ['setChildrenStructureType', 0],
115
                ['persistPageData', -2000],
116 55
            ],
117
            Events::REMOVE => [
118
                ['handleRemovePage', -500],
119
            ],
120
            Events::PUBLISH => [
121
                ['handleScheduleIndex', 0],
122 55
                ['synchronizeChildren', 0],
123
                ['publishChildren', 0],
124
                ['persistPageData', -2000],
125
            ],
126
            Events::REORDER => [['persistPageDataOnReorder', -2000]],
127
            Events::REMOVE_DRAFT => [['handleScheduleIndex', -1024], ['removeDraftChildren', 0]],
128
            Events::FLUSH => [['handleFlush', -2048], ['handleFlushLive', -2048]],
129 55
            Events::COPY => ['handleCopy'],
130 55
            Events::METADATA_LOAD => ['handleMetadataLoad'],
131 55
        ];
132 55
    }
133 55
134 55
    public function handleUuid(PersistEvent $event)
135
    {
136
        $document = $event->getDocument();
137
        if (!$document instanceof ArticleDocument) {
138
            return;
139
        }
140
141
        $event->getNode()->addMixin('mix:referenceable');
142
        $event->getNode()->setProperty('jcr:uuid', $document->getUuid());
143 55
    }
144
145 55
    /**
146 55
     * Schedule article document for index.
147 17
     *
148 9
     * @param AbstractMappingEvent $event
149
     */
150 View Code Duplication
    public function handleScheduleIndex(AbstractMappingEvent $event)
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...
151 8
    {
152
        $document = $event->getDocument();
153
        if (!$document instanceof ArticlePageDocument) {
154 54
            return;
155 54
        }
156 54
157
        $document = $document->getParent();
158 54
159
        $this->documents[$document->getUuid()] = [
160
            'uuid' => $document->getUuid(),
161
            'locale' => $document->getLocale(),
162
        ];
163
    }
164
165 26
    /**
166
     * Schedule article document for live index.
167 26
     *
168 26
     * @param AbstractMappingEvent $event
169 9
     */
170 9 View Code Duplication
    public function handleScheduleIndexLive(AbstractMappingEvent $event)
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...
171
    {
172
        $document = $event->getDocument();
173
        if (!$document instanceof ArticlePageDocument) {
174
            return;
175
        }
176 20
177 20
        $document = $document->getParent();
178 20
179
        $this->liveDocuments[$document->getUuid()] = [
180 20
            'uuid' => $document->getUuid(),
181
            'locale' => $document->getLocale(),
182
        ];
183
    }
184
185
    /**
186
     * Syncs children between live and draft.
187 28
     *
188
     * @param PublishEvent $event
189 28
     */
190 28
    public function synchronizeChildren(PublishEvent $event)
191 9
    {
192
        $document = $event->getDocument();
193
        if (!$document instanceof ArticleDocument) {
194 22
            return;
195 22
        }
196
197 22
        $liveNode = $event->getNode();
198 22
        $draftNode = $this->documentInspector->getNode($document);
199 22
200
        $liveChildren = $this->getChildren($liveNode);
201 22
        $draftChildren = $this->getChildren($draftNode);
202 2
        $removedChildrenIds = array_diff(array_keys($liveChildren), array_keys($draftChildren));
203
204 22
        foreach ($removedChildrenIds as $removedChildrenId) {
205
            $liveChildren[$removedChildrenId]->remove();
206
        }
207
    }
208
209
    /**
210
     * Returns children of given node.
211
     *
212
     * @param NodeInterface $node
213 22
     *
214
     * @return NodeInterface[]
215 22
     */
216 22
    private function getChildren(NodeInterface $node)
217 4
    {
218
        $result = [];
219
        foreach ($node->getNodes() as $child) {
220 22
            $result[$child->getIdentifier()] = $child;
221
        }
222
223
        return $result;
224
    }
225
226
    /**
227
     * Publish pages when article will be published.
228 25
     *
229
     * @param PublishEvent $event
230 25
     */
231 25
    public function publishChildren(PublishEvent $event)
232 9
    {
233
        $document = $event->getDocument();
234
        if (!$document instanceof ArticleDocument) {
235 19
            return;
236 19
        }
237 1
238 1
        $children = iterator_to_array($document->getChildren());
239
        foreach ($children as $child) {
240
            if (LocalizationState::GHOST !== $this->documentInspector->getLocalizationState($child)) {
241 19
                $this->documentManager->publish($child, $event->getLocale());
242
            }
243
        }
244
    }
245
246
    /**
247
     * Persist page-data for reordering children.
248 2
     *
249
     * @param ReorderEvent $event
250 2
     */
251 2
    public function persistPageDataOnReorder(ReorderEvent $event)
252
    {
253
        $document = $event->getDocument();
254
        if (!$document instanceof ArticlePageDocument) {
255 2
            return;
256 2
        }
257
258 2
        $document = $document->getParent();
259
        $node = $this->documentInspector->getNode($document);
260 2
261 2
        $this->setPageData($document, $node, $document->getLocale());
262
263 2
        $document->setWorkflowStage(WorkflowStage::TEST);
264 2
        $this->documentManager->persist($document, $this->documentInspector->getLocale($document));
265 2
266
        $this->documents[$document->getUuid()] = [
267 2
            'uuid' => $document->getUuid(),
268
            'locale' => $document->getLocale(),
269
        ];
270
    }
271
272
    /**
273
     * Persist page-data.
274 54
     *
275
     * @param PersistEvent|PublishEvent $event
276 54
     */
277 54
    public function persistPageData($event)
278 17
    {
279
        $document = $event->getDocument();
280
        if (!$document instanceof ArticleDocument) {
281 53
            return;
282 53
        }
283
284
        $this->setPageData($document, $event->getNode(), $event->getLocale());
285
    }
286
287
    /**
288
     * Set page-data for given document on given node.
289
     *
290
     * @param ArticleDocument $document
291 54
     * @param NodeInterface $node
292
     * @param string $locale
293
     */
294
    private function setPageData(ArticleDocument $document, NodeInterface $node, $locale)
295 54
    {
296 54
        $pages = [
297 54
            [
298 54
                'uuid' => $document->getUuid(),
299
                'title' => $document->getPageTitle() ?: $document->getTitle(),
300
                'routePath' => $document->getRoutePath(),
301
                'pageNumber' => $document->getPageNumber(),
302 54
            ],
303 10
        ];
304 10
305
        foreach ($document->getChildren() as $child) {
306 10
            if ($child instanceof ArticlePageDocument
307 10
                && LocalizationState::GHOST !== $this->documentInspector->getLocalizationState($child)
308 10
            ) {
309 10
                $pages[] = [
310 10
                    'uuid' => $child->getUuid(),
311
                    'title' => $child->getPageTitle(),
312
                    'routePath' => $child->getRoutePath(),
313
                    'pageNumber' => $child->getPageNumber(),
314
                ];
315 54
            }
316
        }
317 54
318 54
        $pages = SortUtils::multisort($pages, '[pageNumber]');
319 54
320 54
        $document->setPages($pages);
321
        $node->setProperty(
322 54
            $this->propertyEncoder->localizedSystemName(self::PAGES_PROPERTY, $locale),
323
            json_encode($pages)
324
        );
325
    }
326
327
    /**
328
     * Hydrate page-data.
329 54
     *
330
     * @param HydrateEvent $event
331 54
     */
332 54
    public function hydratePageData(HydrateEvent $event)
333 52
    {
334
        $document = $event->getDocument();
335
        if (!$document instanceof ArticleDocument) {
336 53
            return;
337 53
        }
338 53
339
        $pages = $event->getNode()->getPropertyValueWithDefault(
340 53
            $this->propertyEncoder->localizedSystemName(self::PAGES_PROPERTY, $event->getLocale()),
341 53
            json_encode([])
342
        );
343
        $document->setPages(json_decode($pages, true));
344
    }
345
346
    /**
347
     * Remove draft from children.
348 2
     *
349
     * @param RemoveDraftEvent $event
350 2
     */
351 2
    public function removeDraftChildren(RemoveDraftEvent $event)
352
    {
353
        $document = $event->getDocument();
354
        if (!$document instanceof ArticleDocument) {
355 2
            return;
356 2
        }
357
358
        foreach ($document->getChildren() as $child) {
359
            if (LocalizationState::GHOST === $this->documentInspector->getLocalizationState($child)) {
360
                continue;
361 2
            }
362 1
363
            try {
364 1
                $this->documentManager->removeDraft($child, $event->getLocale());
365 2
            } catch (PathNotFoundException $exception) {
366
                // child is not available in live workspace
367
                $node = $this->documentInspector->getNode($child);
368 2
                $node->remove();
369
            }
370
        }
371
    }
372
373
    /**
374
     * Index all scheduled article documents with default indexer.
375 54
     *
376
     * @param FlushEvent $event
377 54
     */
378 11 View Code Duplication
    public function handleFlush(FlushEvent $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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...
379
    {
380
        if (count($this->documents) < 1) {
381 53
            return;
382 53
        }
383 53
384
        foreach ($this->documents as $documentData) {
385 53
            $document = $this->documentManager->find($documentData['uuid'], $documentData['locale']);
386
            $this->documentManager->refresh($document, $documentData['locale']);
0 ignored issues
show
Unused Code introduced by
The call to DocumentManagerInterface::refresh() has too many arguments starting with $documentData['locale'].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
387 53
388 53
            $this->indexer->index($document);
389 53
        }
390
        $this->indexer->flush();
391
        $this->documents = [];
392
    }
393
394
    /**
395
     * Index all scheduled article documents with live indexer.
396 54
     *
397
     * @param FlushEvent $event
398 54
     */
399 38 View Code Duplication
    public function handleFlushLive(FlushEvent $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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...
400
    {
401
        if (count($this->liveDocuments) < 1) {
402 20
            return;
403 20
        }
404 20
405
        foreach ($this->liveDocuments as $documentData) {
406 20
            $document = $this->documentManager->find($documentData['uuid'], $documentData['locale']);
407
            $this->documentManager->refresh($document, $documentData['locale']);
0 ignored issues
show
Unused Code introduced by
The call to DocumentManagerInterface::refresh() has too many arguments starting with $documentData['locale'].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
408 20
409 20
            $this->liveIndexer->index($document);
410 20
        }
411
        $this->liveIndexer->flush();
412
        $this->liveDocuments = [];
413
    }
414
415
    /**
416
     * Removes document from live index and unpublish document in default index.
417
     *
418
     * @param UnpublishEvent $event
419
     */
420
    public function handleUnpublish(UnpublishEvent $event)
421
    {
422
        $document = $event->getDocument();
423
        if (!$document instanceof ArticleDocument) {
424
            return;
425
        }
426
427
        $this->liveIndexer->remove($document);
0 ignored issues
show
Documentation introduced by
$document is of type object<Sulu\Bundle\Artic...cument\ArticleDocument>, but the function expects a string.

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...
428
        $this->liveIndexer->flush();
429
430
        $this->indexer->setUnpublished($document->getUuid(), $event->getLocale());
431
        $this->indexer->flush();
432
    }
433
434
    /**
435 4
     * Reindex article if a page was removed.
436
     *
437 4
     * @param RemoveEvent $event
438 4
     */
439 3 View Code Duplication
    public function handleRemovePage(RemoveEvent $event)
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...
440
    {
441
        $document = $event->getDocument();
442 1
        if (!$document instanceof ArticlePageDocument) {
443 1
            return;
444 1
        }
445 1
446
        $document = $document->getParent();
447 1
        $this->documents[$document->getUuid()] = [
448
            'uuid' => $document->getUuid(),
449
            'locale' => $document->getLocale(),
450
        ];
451
    }
452
453
    /**
454 4
     * Schedule document to index.
455
     *
456 4
     * @param CopyEvent $event
457 4
     */
458 3
    public function handleCopy(CopyEvent $event)
459
    {
460
        $document = $event->getDocument();
461 1
        if (!$document instanceof ArticleDocument) {
462 1
            return;
463 1
        }
464 1
465
        $uuid = $event->getCopiedNode()->getIdentifier();
466 1
        $this->documents[$uuid] = [
467
            'uuid' => $uuid,
468
            'locale' => $document->getLocale(),
469
        ];
470
    }
471
472
    /**
473 5
     * Set structure-type to pages.
474
     *
475 5
     * @param PersistEvent $event
476 5
     */
477 2
    public function setChildrenStructureType(PersistEvent $event)
478
    {
479
        $document = $event->getDocument();
480 3
        if (!$document instanceof ArticleDocument) {
481 3
            return;
482 3
        }
483
484
        foreach ($document->getChildren() as $child) {
485
            if (LocalizationState::GHOST !== $this->documentInspector->getLocalizationState($child)
486
                && $document->getStructureType() !== $child->getStructureType()
487
            ) {
488
                $child->setStructureType($document->getStructureType());
489 5
                $this->documentManager->persist($child, $event->getLocale(), $event->getOptions());
0 ignored issues
show
Documentation introduced by
$event->getOptions() is of type object<Symfony\Component...solver\OptionsResolver>, but the function expects a array.

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...
490
            }
491 5
        }
492 5
    }
493 2
494
    /**
495
     * Extend metadata for article-page.
496 3
     *
497 3
     * @param MetadataLoadEvent $event
498 3
     */
499
    public function handleMetadataLoad(MetadataLoadEvent $event)
500
    {
501
        if (ArticleDocument::class !== $event->getMetadata()->getClass()) {
502
            return;
503
        }
504
505
        $event->getMetadata()->addFieldMapping(
506
            'pageTitle',
507
            [
508
                'encoding' => 'system_localized',
509
                'property' => 'suluPageTitle',
510
            ]
511
        );
512
    }
513
}
514