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

ArticleSubscriber::handleRemoveLive()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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