Completed
Pull Request — develop (#179)
by Wachter
13:33
created

ArticleSubscriber::handleScheduleIndex()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 16
Ratio 100 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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