Completed
Pull Request — develop (#147)
by Wachter
14:07
created

ArticleIndexer   D

Complexity

Total Complexity 37

Size/Duplication

Total Lines 338
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 25

Test Coverage

Coverage 89.19%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 37
lcom 1
cbo 25
dl 0
loc 338
ccs 99
cts 111
cp 0.8919
rs 4.3
c 1
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 23 1
A getTypeTranslation() 0 10 2
A dispatchIndexEvent() 0 4 1
C createOrUpdateArticle() 0 69 16
B findOrCreateViewDocument() 0 24 4
A mapPages() 0 13 2
A removeArticle() 0 12 2
A remove() 0 10 2
A flush() 0 4 1
A clear() 0 20 3
A setUnpublished() 0 13 2
A index() 0 6 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\Index;
13
14
use ONGR\ElasticsearchBundle\Collection\Collection;
15
use ONGR\ElasticsearchBundle\Service\Manager;
16
use ONGR\ElasticsearchDSL\Query\MatchAllQuery;
17
use ONGR\ElasticsearchDSL\Query\TermQuery;
18
use Sulu\Bundle\ArticleBundle\Document\ArticleDocument;
19
use Sulu\Bundle\ArticleBundle\Document\ArticlePageViewObject;
20
use Sulu\Bundle\ArticleBundle\Document\ArticleViewDocumentInterface;
21
use Sulu\Bundle\ArticleBundle\Document\Index\Factory\ExcerptFactory;
22
use Sulu\Bundle\ArticleBundle\Document\Index\Factory\SeoFactory;
23
use Sulu\Bundle\ArticleBundle\Document\LocalizationStateViewObject;
24
use Sulu\Bundle\ArticleBundle\Event\Events;
25
use Sulu\Bundle\ArticleBundle\Event\IndexEvent;
26
use Sulu\Bundle\ArticleBundle\Metadata\ArticleTypeTrait;
27
use Sulu\Bundle\ArticleBundle\Metadata\ArticleViewDocumentIdTrait;
28
use Sulu\Bundle\ContactBundle\Entity\ContactRepository;
29
use Sulu\Bundle\SecurityBundle\UserManager\UserManager;
30
use Sulu\Component\Content\Document\LocalizationState;
31
use Sulu\Component\Content\Document\WorkflowStage;
32
use Sulu\Component\Content\Metadata\Factory\StructureMetadataFactoryInterface;
33
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
34
use Symfony\Component\Translation\TranslatorInterface;
35
36
/**
37
 * Provides methods to index articles.
38
 */
39
class ArticleIndexer implements IndexerInterface
40
{
41
    use ArticleTypeTrait;
42
    use ArticleViewDocumentIdTrait;
43
44
    /**
45
     * @var StructureMetadataFactoryInterface
46
     */
47
    protected $structureMetadataFactory;
48
49
    /**
50
     * @var UserManager
51
     */
52
    protected $userManager;
53
54
    /**
55
     * @var ContactRepository
56
     */
57
    protected $contactRepository;
58
59
    /**
60
     * @var DocumentFactoryInterface
61
     */
62
    protected $documentFactory;
63
64
    /**
65
     * @var Manager
66
     */
67
    protected $manager;
68
69
    /**
70
     * @var ExcerptFactory
71
     */
72
    protected $excerptFactory;
73
74
    /**
75
     * @var SeoFactory
76
     */
77
    protected $seoFactory;
78
79
    /**
80
     * @var EventDispatcherInterface
81
     */
82
    protected $eventDispatcher;
83
84
    /**
85
     * @var TranslatorInterface
86
     */
87
    protected $translator;
88
89
    /**
90
     * @var array
91
     */
92
    protected $typeConfiguration;
93
94
    /**
95
     * @param StructureMetadataFactoryInterface $structureMetadataFactory
96
     * @param UserManager $userManager
97
     * @param ContactRepository $contactRepository
98
     * @param DocumentFactoryInterface $documentFactory
99
     * @param Manager $manager
100
     * @param ExcerptFactory $excerptFactory
101
     * @param SeoFactory $seoFactory
102
     * @param EventDispatcherInterface $eventDispatcher
103
     * @param TranslatorInterface $translator
104
     * @param array $typeConfiguration
105 30
     */
106
    public function __construct(
107
        StructureMetadataFactoryInterface $structureMetadataFactory,
108
        UserManager $userManager,
109
        ContactRepository $contactRepository,
110
        DocumentFactoryInterface $documentFactory,
111
        Manager $manager,
112
        ExcerptFactory $excerptFactory,
113
        SeoFactory $seoFactory,
114
        EventDispatcherInterface $eventDispatcher,
115
        TranslatorInterface $translator,
116
        array $typeConfiguration
117 30
    ) {
118 30
        $this->structureMetadataFactory = $structureMetadataFactory;
119 30
        $this->userManager = $userManager;
120 30
        $this->contactRepository = $contactRepository;
121 30
        $this->documentFactory = $documentFactory;
122 30
        $this->manager = $manager;
123 30
        $this->excerptFactory = $excerptFactory;
124 30
        $this->seoFactory = $seoFactory;
125 30
        $this->eventDispatcher = $eventDispatcher;
126 30
        $this->translator = $translator;
127 30
        $this->typeConfiguration = $typeConfiguration;
128
    }
129
130
    /**
131
     * Returns translation for given article type.
132
     *
133
     * @param string $type
134
     *
135
     * @return string
136 30
     */
137
    private function getTypeTranslation($type)
138 30
    {
139 30
        if (!array_key_exists($type, $this->typeConfiguration)) {
140
            return ucfirst($type);
141
        }
142
143
        $typeTranslationKey = $this->typeConfiguration[$type]['translation_key'];
144
145
        return $this->translator->trans($typeTranslationKey, [], 'backend');
146
    }
147
148
    /**
149
     * @param ArticleDocument $document
150
     * @param ArticleViewDocumentInterface $article
151
     */
152
    protected function dispatchIndexEvent(ArticleDocument $document, ArticleViewDocumentInterface $article)
153
    {
154
        $this->eventDispatcher->dispatch(Events::INDEX_EVENT, new IndexEvent($document, $article));
155 30
    }
156
157 30
    /**
158 30
     * @param ArticleDocument $document
159
     * @param string $locale
160
     * @param string $localizationState
161
     *
162
     * @return ArticleViewDocumentInterface
163
     */
164
    protected function createOrUpdateArticle(
165
        ArticleDocument $document,
166
        $locale,
167 30
        $localizationState = LocalizationState::LOCALIZED
168
    ) {
169
        $article = $this->findOrCreateViewDocument($document, $locale, $localizationState);
170
        if (!$article) {
171
            return;
172 30
        }
173
174 30
        $structureMetadata = $this->structureMetadataFactory->getStructureMetadata(
175
            'article',
176 30
            $document->getStructureType()
177 30
        );
178 30
179 30
        $article->setTitle($document->getTitle());
180 30
        $article->setRoutePath($document->getRoutePath());
181
        $article->setChanged($document->getChanged());
182
        $article->setCreated($document->getCreated());
183 9
        $article->setAuthored($document->getAuthored());
184 9
        if ($document->getAuthor() && $author = $this->contactRepository->find($document->getAuthor())) {
185
            $article->setAuthorFullName($author->getFullName());
186 4
            $article->setAuthorId($author->getId());
187
        }
188
        if ($document->getChanger() && $changer = $this->userManager->getUserById($document->getChanger())) {
189
            $article->setChangerFullName($changer->getFullName());
190 30
            $article->setChangerContactId($changer->getContact()->getId());
191 30
        }
192 30
        if ($document->getCreator() && $creator = $this->userManager->getUserById($document->getCreator())) {
193
            $article->setCreatorFullName($creator->getFullName());
194
            $article->setCreatorContactId($creator->getContact()->getId());
195 30
        }
196 30
        $article->setType($this->getType($structureMetadata));
0 ignored issues
show
Bug introduced by
It seems like $structureMetadata defined by $this->structureMetadata...nt->getStructureType()) on line 174 can be null; however, Sulu\Bundle\ArticleBundl...cleTypeTrait::getType() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
197 30
        $article->setStructureType($document->getStructureType());
198 30
        $article->setPublished($document->getPublished());
0 ignored issues
show
Documentation introduced by
$document->getPublished() is of type boolean, but the function expects a null|object<DateTime>.

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...
199 30
        $article->setPublishedState($document->getWorkflowStage() === WorkflowStage::PUBLISHED);
200 30
        $article->setTypeTranslation($this->getTypeTranslation($this->getType($structureMetadata)));
0 ignored issues
show
Bug introduced by
It seems like $structureMetadata defined by $this->structureMetadata...nt->getStructureType()) on line 174 can be null; however, Sulu\Bundle\ArticleBundl...cleTypeTrait::getType() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
201 30
        $article->setLocalizationState(
202 30
            new LocalizationStateViewObject(
203
                $localizationState,
204 30
                (LocalizationState::LOCALIZED === $localizationState) ? null : $document->getLocale()
205 30
            )
206 30
        );
207
208 30
        $extensions = $document->getExtensionsData()->toArray();
209 30
        if (array_key_exists('excerpt', $extensions)) {
210 30
            $article->setExcerpt($this->excerptFactory->create($extensions['excerpt'], $document->getLocale()));
211
        }
212 30
        if (array_key_exists('seo', $extensions)) {
213 30
            $article->setSeo($this->seoFactory->create($extensions['seo']));
214 30
        }
215 30
        if ($structureMetadata->hasPropertyWithTagName('sulu.teaser.description')) {
216 30
            $descriptionProperty = $structureMetadata->getPropertyByTagName('sulu.teaser.description');
217 30
            $article->setTeaserDescription(
218 30
                $document->getStructure()->getProperty($descriptionProperty->getName())->getValue()
0 ignored issues
show
Documentation introduced by
$document->getStructure(...>getName())->getValue() is of type array<integer|string,*>, 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...
219
            );
220 30
        }
221
        if ($structureMetadata->hasPropertyWithTagName('sulu.teaser.media')) {
222
            $mediaProperty = $structureMetadata->getPropertyByTagName('sulu.teaser.media');
223
            $mediaData = $document->getStructure()->getProperty($mediaProperty->getName())->getValue();
224 30
            if (null !== $mediaData && array_key_exists('ids', $mediaData)) {
225 30
                $article->setTeaserMediaId(reset($mediaData['ids']) ?: null);
226 30
            }
227
        }
228 30
229 30
        $this->mapPages($document, $article);
230
231 30
        return $article;
232 1
    }
233 1
234 1
    /**
235
     * Returns view-document from index or create a new one.
236
     *
237 30
     * @param ArticleDocument $document
238 1
     * @param string $locale
239 1
     * @param string $localizationState
240 1
     *
241 1
     * @return ArticleViewDocumentInterface
242
     */
243
    protected function findOrCreateViewDocument(ArticleDocument $document, $locale, $localizationState)
244
    {
245 30
        $articleId = $this->getViewDocumentId($document->getUuid(), $locale);
246
        /** @var ArticleViewDocumentInterface $article */
247
        $article = $this->manager->find($this->documentFactory->getClass('article'), $articleId);
248
249
        if ($article) {
250
            // Only index ghosts when the article isn't a ghost himself.
251 2
            if (LocalizationState::GHOST === $localizationState
252
                && LocalizationState::GHOST !== $article->getLocalizationState()->state
253 2
            ) {
254 2
                return null;
255 2
            }
256 2
257 2
            return $article;
258 2
        }
259
260 2
        $article = $this->documentFactory->create('article');
261
        $article->setId($articleId);
262
        $article->setUuid($document->getUuid());
263
        $article->setLocale($locale);
264
265 30
        return $article;
266
    }
267 30
268 30
    /**
269
     * Maps pages from document to view-document.
270
     *
271
     * @param ArticleDocument $document
272
     * @param ArticleViewDocumentInterface $article
273 10
     */
274
    private function mapPages(ArticleDocument $document, ArticleViewDocumentInterface $article)
275 10
    {
276 10
        $pages = [];
277 10
        foreach ($document->getChildren() as $child) {
278 10
            $pages[] = $page = new ArticlePageViewObject();
279 10
            $page->uuid = $child->getUuid();
280
            $page->pageNumber = $child->getPageNumber();
281
            $page->title = $child->getPageTitle();
282 10
            $page->routePath = $child->getRoutePath();
283 10
        }
284 8
285
        $article->setPages(new Collection($pages));
286
    }
287 10
288 10
    /**
289
     * @param string $id
290 10
     */
291 10
    protected function removeArticle($id)
292 10
    {
293
        $article = $this->manager->find(
294
            $this->documentFactory->getClass('article'),
295
            $id
296
        );
297
        if (null === $article) {
298
            return;
299
        }
300
301
        $this->manager->remove($article);
302
    }
303
304
    /**
305
     * {@inheritdoc}
306
     */
307
    public function remove($document)
308
    {
309
        $repository = $this->manager->getRepository($this->documentFactory->getClass('article'));
310
        $search = $repository->createSearch()
311
            ->addQuery(new TermQuery('uuid', $document->getUuid()))
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Query\TermQuery has been deprecated with message: Use the extended class instead. This class is left only for BC compatibility.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
312
            ->setSize(1000);
313
        foreach ($repository->execute($search) as $viewDocument) {
0 ignored issues
show
Deprecated Code introduced by
The method ONGR\ElasticsearchBundle...e\Repository::execute() has been deprecated with message: Use strict execute functions instead. e.g. executeIterator, executeRawIterator.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
314 11
            $this->manager->remove($viewDocument);
315
        }
316 11
    }
317 11
318 11
    /**
319 11
     * {@inheritdoc}
320
     */
321
    public function flush()
322
    {
323
        $this->manager->commit();
324
    }
325
326
    /**
327
     * {@inheritdoc}
328
     */
329
    public function clear()
330
    {
331
        $pageSize = 500;
332
        $repository = $this->manager->getRepository($this->documentFactory->getClass('article'));
333
        $search = $repository->createSearch()
334
            ->addQuery(new MatchAllQuery())
335
            ->setSize($pageSize);
336
337
        do {
338
            $result = $repository->execute($search);
0 ignored issues
show
Deprecated Code introduced by
The method ONGR\ElasticsearchBundle...e\Repository::execute() has been deprecated with message: Use strict execute functions instead. e.g. executeIterator, executeRawIterator.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
339
            foreach ($result as $document) {
340
                $this->manager->remove($document);
341
            }
342
343
            $this->manager->commit();
344
        } while ($result->count() !== 0);
345
346
        $this->manager->clearCache();
347
        $this->manager->flush();
348
    }
349
350
    /**
351
     * {@inheritdoc}
352
     */
353
    public function setUnpublished($uuid)
354
    {
355
        $article = $this->manager->find($this->documentFactory->getClass('article'), $uuid);
356
357
        if (!$article) {
358
            return;
359
        }
360
361
        $article->setPublished(null);
362
        $article->setPublishedState(false);
363
364
        $this->manager->persist($article);
365
    }
366
367
    /**
368
     * {@inheritdoc}
369
     */
370
    public function index(ArticleDocument $document)
371
    {
372
        $article = $this->createOrUpdateArticle($document, $document->getLocale());
373
        $this->dispatchIndexEvent($document, $article);
0 ignored issues
show
Bug introduced by
It seems like $article defined by $this->createOrUpdateArt...$document->getLocale()) on line 372 can be null; however, Sulu\Bundle\ArticleBundl...r::dispatchIndexEvent() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
374
        $this->manager->persist($article);
0 ignored issues
show
Bug introduced by
It seems like $article defined by $this->createOrUpdateArt...$document->getLocale()) on line 372 can be null; however, ONGR\ElasticsearchBundle...vice\Manager::persist() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
375
    }
376
}
377