Completed
Pull Request — develop (#179)
by Wachter
14:46
created

ArticleSubscriber::publishChildren()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 6
cts 6
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 66
     * @param DocumentInspector $documentInspector
86
     * @param PropertyEncoder $propertyEncoder
87
     */
88
    public function __construct(
89
        IndexerInterface $indexer,
90
        IndexerInterface $liveIndexer,
91
        DocumentManagerInterface $documentManager,
92 66
        DocumentInspector $documentInspector,
93 66
        PropertyEncoder $propertyEncoder
94 66
    ) {
95 66
        $this->indexer = $indexer;
96 66
        $this->liveIndexer = $liveIndexer;
97 66
        $this->documentManager = $documentManager;
98
        $this->documentInspector = $documentInspector;
99
        $this->propertyEncoder = $propertyEncoder;
100
    }
101
102 52
    /**
103
     * {@inheritdoc}
104
     */
105 52
    public static function getSubscribedEvents()
106
    {
107
        return [
108 52
            Events::HYDRATE => [
109
                ['hydratePageData', -2000],
110
            ],
111
            Events::PERSIST => [
112
                ['handleScheduleIndex', -500],
113 52
                ['setChildrenStructureType', 0],
114
                ['persistPageData', -2000],
115
            ],
116
            Events::REMOVE => [
117
                ['handleRemove', -500],
118
                ['handleRemoveLive', -500],
119 52
                ['handleRemovePage', -500],
120
                ['handleRemovePageLive', -500],
121
            ],
122
            Events::PUBLISH => [
123
                ['handleScheduleIndexLive', 0],
124
                ['handleScheduleIndex', 0],
125
                ['synchronizeChildren', 0],
126 52
                ['publishChildren', 0],
127 52
                ['persistPageData', -2000],
128 52
            ],
129 52
            Events::REORDER => [['persistPageDataOnReorder', -2000]],
130 52
            Events::UNPUBLISH => 'handleUnpublish',
131
            Events::REMOVE_DRAFT => [['handleScheduleIndex', -1024], ['removeDraftChildren', 0]],
132
            Events::FLUSH => [['handleFlush', -2048], ['handleFlushLive', -2048]],
133
            Events::COPY => ['handleCopy'],
134
            Events::METADATA_LOAD => ['handleMetadataLoad'],
135
        ];
136
    }
137
138
    /**
139 52
     * Schedule article document for index.
140
     *
141 52
     * @param AbstractMappingEvent $event
142 52
     */
143 15 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...
144 8
    {
145
        $document = $event->getDocument();
146
        if (!$document instanceof ArticleDocument) {
147 7
            if (!$document instanceof ArticlePageDocument) {
148
                return;
149
            }
150 51
151 51
            $document = $document->getParent();
152 51
        }
153
154 51
        $this->documents[$document->getUuid()] = [
155
            'uuid' => $document->getUuid(),
156
            'locale' => $document->getLocale(),
157
        ];
158
    }
159
160
    /**
161 24
     * Schedule article document for live index.
162
     *
163 24
     * @param AbstractMappingEvent $event
164 24
     */
165 8 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...
166 8
    {
167
        $document = $event->getDocument();
168
        if (!$document instanceof ArticleDocument) {
169
            if (!$document instanceof ArticlePageDocument) {
170
                return;
171
            }
172 18
173 18
            $document = $document->getParent();
174 18
        }
175
176 18
        $this->liveDocuments[$document->getUuid()] = [
177
            'uuid' => $document->getUuid(),
178
            'locale' => $document->getLocale(),
179
        ];
180
    }
181
182
    /**
183 26
     * Syncs children between live and draft.
184
     *
185 26
     * @param PublishEvent $event
186 26
     */
187 8
    public function synchronizeChildren(PublishEvent $event)
188
    {
189
        $document = $event->getDocument();
190 20
        if (!$document instanceof ArticleDocument) {
191 20
            return;
192
        }
193 20
194 20
        $liveNode = $event->getNode();
195 20
        $draftNode = $this->documentInspector->getNode($document);
196
197 20
        $liveChildren = $this->getChildren($liveNode);
198 2
        $draftChildren = $this->getChildren($draftNode);
199
        $removedChildrenIds = array_diff(array_keys($liveChildren), array_keys($draftChildren));
200 20
201
        foreach ($removedChildrenIds as $removedChildrenId) {
202
            $liveChildren[$removedChildrenId]->remove();
203
        }
204
    }
205
206
    /**
207
     * Returns children of given node.
208
     *
209 20
     * @param NodeInterface $node
210
     *
211 20
     * @return NodeInterface[]
212 20
     */
213 4
    private function getChildren(NodeInterface $node)
214
    {
215
        $result = [];
216 20
        foreach ($node->getNodes() as $child) {
217
            $result[$child->getIdentifier()] = $child;
218
        }
219
220
        return $result;
221
    }
222
223
    /**
224 23
     * Publish pages when article will be published.
225
     *
226 23
     * @param PublishEvent $event
227 23
     */
228 8
    public function publishChildren(PublishEvent $event)
229
    {
230
        $document = $event->getDocument();
231 17
        if (!$document instanceof ArticleDocument) {
232 17
            return;
233 1
        }
234 1
235
        $children = iterator_to_array($document->getChildren());
236
        foreach ($children as $child) {
237 17
            if ($this->documentInspector->getLocalizationState($child) !== LocalizationState::GHOST) {
238
                $this->documentManager->publish($child, $event->getLocale());
239
            }
240
        }
241
    }
242
243
    /**
244 51
     * Persist page-data for reordering children.
245
     *
246 51
     * @param ReorderEvent $event
247 51
     */
248 15
    public function persistPageDataOnReorder(ReorderEvent $event)
249
    {
250
        $document = $event->getDocument();
251
        if (!$document instanceof ArticlePageDocument) {
252
            return;
253 50
        }
254 50
255 50
        $document = $document->getParent();
256 50
        $node = $this->documentInspector->getNode($document);
257
258
        $this->setPageData($document, $node, $document->getLocale());
259
260 50
        $document->setWorkflowStage(WorkflowStage::TEST);
261 8
        $this->documentManager->persist($document, $this->documentInspector->getLocale($document));
262 8
263
        $this->documents[$document->getUuid()] = [
264 8
            'uuid' => $document->getUuid(),
265 8
            'locale' => $document->getLocale(),
266 8
        ];
267 8
    }
268 8
269
    /**
270
     * Persist page-data.
271
     *
272
     * @param PersistEvent|PublishEvent $event
273 50
     */
274 50
    public function persistPageData($event)
275 50
    {
276 50
        $document = $event->getDocument();
277
        if (!$document instanceof ArticleDocument) {
278 50
            return;
279
        }
280
281
        $this->setPageData($document, $event->getNode(), $event->getLocale());
282
    }
283
284
    /**
285 51
     * Set page-data for given document on given node.
286
     *
287 51
     * @param ArticleDocument $document
288 51
     * @param NodeInterface $node
289 49
     * @param string $locale
290
     */
291
    private function setPageData(ArticleDocument $document, NodeInterface $node, $locale)
292 50
    {
293 50
        $pages = [
294 50
            [
295
                'uuid' => $document->getUuid(),
296 50
                'title' => $document->getPageTitle() ?: $document->getTitle(),
297 50
                'routePath' => $document->getRoutePath(),
298
                'pageNumber' => $document->getPageNumber(),
299
            ],
300
        ];
301
302
        foreach ($document->getChildren() as $child) {
303
            if ($child instanceof ArticlePageDocument
304 2
                && $this->documentInspector->getLocalizationState($child) !== LocalizationState::GHOST
305
            ) {
306 2
                $pages[] = [
307 2
                    'uuid' => $child->getUuid(),
308
                    'title' => $child->getPageTitle(),
309
                    'routePath' => $child->getRoutePath(),
310
                    'pageNumber' => $child->getPageNumber(),
311 2
                ];
312 2
            }
313
        }
314
315
        $pages = SortUtils::multisort($pages, '[pageNumber]');
316
317 2
        $document->setPages($pages);
318 1
        $node->setProperty(
319
            $this->propertyEncoder->localizedSystemName(self::PAGES_PROPERTY, $locale),
320 1
            json_encode($pages)
321 2
        );
322
    }
323
324 2
    /**
325
     * Hydrate page-data.
326
     *
327
     * @param HydrateEvent $event
328
     */
329
    public function hydratePageData(HydrateEvent $event)
330
    {
331 51
        $document = $event->getDocument();
332
        if (!$document instanceof ArticleDocument) {
333 51
            return;
334 10
        }
335
336
        $pages = $event->getNode()->getPropertyValueWithDefault(
337 50
            $this->propertyEncoder->localizedSystemName(self::PAGES_PROPERTY, $event->getLocale()),
338 50
            json_encode([])
339 50
        );
340
        $document->setPages(json_decode($pages, true));
341 50
    }
342
343 50
    /**
344 50
     * Remove draft from children.
345 50
     *
346
     * @param RemoveDraftEvent $event
347
     */
348
    public function removeDraftChildren(RemoveDraftEvent $event)
349
    {
350
        $document = $event->getDocument();
351
        if (!$document instanceof ArticleDocument) {
352 51
            return;
353
        }
354 51
355 36
        foreach ($document->getChildren() as $child) {
356
            if ($this->documentInspector->getLocalizationState($child) === LocalizationState::GHOST) {
357
                continue;
358 18
            }
359 18
360 18
            try {
361
                $this->documentManager->removeDraft($child, $event->getLocale());
362 18
            } catch (PathNotFoundException $exception) {
363
                // child is not available in live workspace
364 18
                $node = $this->documentInspector->getNode($child);
365 18
                $node->remove();
366 18
            }
367
        }
368
    }
369
370
    /**
371
     * Index all scheduled article documents with default indexer.
372
     *
373
     * @param FlushEvent $event
374
     */
375 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...
376
    {
377
        if (count($this->documents) < 1) {
378
            return;
379
        }
380
381
        foreach ($this->documents as $documentData) {
382
            $document = $this->documentManager->find($documentData['uuid'], $documentData['locale']);
383
            $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...
384
385
            $this->indexer->index($document);
386
        }
387
        $this->indexer->flush();
388
        $this->documents = [];
389
    }
390
391 4
    /**
392
     * Index all scheduled article documents with live indexer.
393 4
     *
394 4
     * @param FlushEvent $event
395 3
     */
396 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...
397
    {
398 1
        if (count($this->liveDocuments) < 1) {
399 1
            return;
400 1
        }
401 1
402
        foreach ($this->liveDocuments as $documentData) {
403 1
            $document = $this->documentManager->find($documentData['uuid'], $documentData['locale']);
404
            $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...
405
406
            $this->liveIndexer->index($document);
407
        }
408
        $this->liveIndexer->flush();
409
        $this->liveDocuments = [];
410 4
    }
411
412 4
    /**
413 4
     * Removes document from live index and unpublish document in default index.
414 3
     *
415
     * @param UnpublishEvent $event
416
     */
417 1
    public function handleUnpublish(UnpublishEvent $event)
418 1
    {
419 1
        $document = $event->getDocument();
420 1
        if (!$document instanceof ArticleDocument) {
421
            return;
422 1
        }
423
424
        $this->liveIndexer->remove($document);
425
        $this->liveIndexer->flush();
426
427
        $this->indexer->setUnpublished($document->getUuid());
428
    }
429 5
430
    /**
431 5
     * Reindex article if a page was removed.
432 5
     *
433 2
     * @param RemoveEvent $event
434
     */
435
    public function handleRemovePage(RemoveEvent $event)
436 3
    {
437 3
        $document = $event->getDocument();
438 3
        if (!$document instanceof ArticlePageDocument) {
439
            return;
440
        }
441
442
        $document = $document->getParent();
443
        $this->documents[$document->getUuid()] = [
444
            'uuid' => $document->getUuid(),
445 5
            'locale' => $document->getLocale(),
446
        ];
447 5
    }
448 5
449 2
    /**
450
     * Reindex article live if a page was removed.
451
     *
452 3
     * @param RemoveEvent $event
453 3
     */
454 3
    public function handleRemovePageLive(RemoveEvent $event)
455
    {
456
        $document = $event->getDocument();
457
        if (!$document instanceof ArticlePageDocument) {
458
            return;
459
        }
460
461
        $document = $document->getParent();
462
        $this->liveDocuments[$document->getUuid()] = [
463
            'uuid' => $document->getUuid(),
464
            'locale' => $document->getLocale(),
465
        ];
466
    }
467
468
    /**
469
     * Removes article-document.
470
     *
471
     * @param RemoveEvent $event
472
     */
473
    public function handleRemove(RemoveEvent $event)
474
    {
475
        $document = $event->getDocument();
476
        if (!$document instanceof ArticleDocument) {
477
            return;
478
        }
479
480 51
        $this->indexer->remove($document);
481
        $this->indexer->flush();
482 51
    }
483 51
484 15
    /**
485
     * Removes article-document.
486
     *
487 50
     * @param RemoveEvent|UnpublishEvent $event
488 8
     */
489 8
    public function handleRemoveLive($event)
490
    {
491 2
        $document = $event->getDocument();
492 8
        if (!$document instanceof ArticleDocument) {
493
            return;
494
        }
495 50
496
        $this->liveIndexer->remove($document);
497
        $this->liveIndexer->flush();
498
    }
499
500
    /**
501
     * Schedule document to index.
502 50
     *
503
     * @param CopyEvent $event
504 50
     */
505 15
    public function handleCopy(CopyEvent $event)
506
    {
507
        $document = $event->getDocument();
508 49
        if (!$document instanceof ArticleDocument) {
509 49
            return;
510
        }
511 49
512
        $uuid = $event->getCopiedNode()->getIdentifier();
513
        $this->documents[$uuid] = [
514
            'uuid' => $uuid,
515 49
            'locale' => $document->getLocale(),
516
        ];
517
    }
518
519
    /**
520
     * Set structure-type to pages.
521
     *
522
     * @param PersistEvent $event
523
     */
524
    public function setChildrenStructureType(PersistEvent $event)
525
    {
526
        $document = $event->getDocument();
527
        if (!$document instanceof ArticleDocument) {
528
            return;
529
        }
530
531
        foreach ($document->getChildren() as $child) {
532
            if ($this->documentInspector->getLocalizationState($child) !== LocalizationState::GHOST
533
                && $document->getStructureType() !== $child->getStructureType()
534
            ) {
535
                $child->setStructureType($document->getStructureType());
536
                $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...
537
            }
538
        }
539
    }
540
541
    /**
542
     * Extend metadata for article-page.
543
     *
544
     * @param MetadataLoadEvent $event
545
     */
546
    public function handleMetadataLoad(MetadataLoadEvent $event)
547
    {
548
        if ($event->getMetadata()->getClass() !== ArticleDocument::class) {
549
            return;
550
        }
551
552
        $event->getMetadata()->addFieldMapping(
553
            'pageTitle',
554
            [
555
                'encoding' => 'system_localized',
556
                'property' => 'suluPageTitle',
557
            ]
558
        );
559
    }
560
}
561