Completed
Push — master ( 257818...6d6774 )
by Jeroen
06:17 queued 13s
created

NodePagesConfiguration   F

Complexity

Total Complexity 80

Size/Duplication

Total Lines 754
Duplicated Lines 1.19 %

Coupling/Cohesion

Components 1
Dependencies 23

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 80
lcom 1
cbo 23
dl 9
loc 754
ccs 0
cts 281
cp 0
rs 1.846
c 0
b 0
f 0

31 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A setAclProvider() 0 4 1
A setIndexablePagePartsService() 0 4 1
A setDefaultProperties() 0 4 1
A setLogger() 0 4 1
A getLanguagesNotAnalyzed() 0 15 4
A createIndex() 0 29 4
A populateIndex() 0 17 4
A indexNode() 0 9 2
A createNodeDocuments() 0 7 3
A indexChildren() 0 6 2
B indexNodeTranslation() 0 32 6
A isIndexable() 0 4 1
A deleteNodeTranslation() 0 6 1
A deleteIndex() 0 6 2
B setAnalysis() 0 36 8
A createDefaultSearchFieldsMapping() 0 13 2
A setMapping() 0 10 2
B addPageToIndex() 0 58 3
A addPermissions() 0 10 2
A addSearchType() 0 4 1
A addParentAndAncestors() 0 14 3
A addPageContent() 0 31 4
A enterRequestScope() 0 19 5
A renderCustomSearchView() 0 27 2
A renderDefaultSearchView() 0 21 1
A addCustomData() 0 11 2
A getUTCDateTime() 0 7 1
A removeHtml() 0 24 3
A getAclPermissions() 9 28 5
A getNodeRefPage() 0 10 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like NodePagesConfiguration often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NodePagesConfiguration, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Kunstmaan\NodeSearchBundle\Configuration;
4
5
use Doctrine\ORM\EntityManager;
6
use Elastica\Index;
7
use Elastica\Type\Mapping;
8
use Kunstmaan\AdminBundle\Helper\DomainConfigurationInterface;
9
use Kunstmaan\AdminBundle\Helper\Security\Acl\Permission\MaskBuilder;
10
use Kunstmaan\NodeBundle\Entity\HasNodeInterface;
11
use Kunstmaan\NodeBundle\Entity\Node;
12
use Kunstmaan\NodeBundle\Entity\NodeTranslation;
13
use Kunstmaan\NodeBundle\Entity\NodeVersion;
14
use Kunstmaan\NodeBundle\Entity\PageInterface;
15
use Kunstmaan\NodeBundle\Helper\RenderContext;
16
use Kunstmaan\NodeSearchBundle\Event\IndexNodeEvent;
17
use Kunstmaan\NodeSearchBundle\Helper\IndexablePagePartsService;
18
use Kunstmaan\NodeSearchBundle\Helper\SearchViewTemplateInterface;
19
use Kunstmaan\PagePartBundle\Helper\HasPagePartsInterface;
20
use Kunstmaan\SearchBundle\Configuration\SearchConfigurationInterface;
21
use Kunstmaan\SearchBundle\Provider\SearchProviderInterface;
22
use Kunstmaan\SearchBundle\Search\AnalysisFactoryInterface;
23
use Kunstmaan\UtilitiesBundle\Helper\ClassLookup;
24
use Psr\Log\LoggerInterface;
25
use Symfony\Component\DependencyInjection\ContainerInterface;
26
use Symfony\Component\DomCrawler\Crawler;
27
use Symfony\Component\HttpFoundation\Request;
28
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
29
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
30
use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
31
use Symfony\Component\Security\Acl\Model\AclInterface;
32
use Symfony\Component\Security\Acl\Model\AclProviderInterface;
33
use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
34
use Symfony\Component\Templating\EngineInterface;
35
36
class NodePagesConfiguration implements SearchConfigurationInterface
37
{
38
    /** @var string */
39
    protected $indexName;
40
41
    /** @var string */
42
    protected $indexType;
43
44
    /** @var SearchProviderInterface */
45
    protected $searchProvider;
46
47
    /** @var array */
48
    protected $locales = [];
49
50
    /** @var array */
51
    protected $analyzerLanguages;
52
53
    /** @var EntityManager */
54
    protected $em;
55
56
    /** @var array */
57
    protected $documents = [];
58
59
    /** @var ContainerInterface */
60
    protected $container;
61
62
    /** @var AclProviderInterface */
63
    protected $aclProvider = null;
64
65
    /** @var LoggerInterface */
66
    protected $logger = null;
67
68
    /** @var IndexablePagePartsService */
69
    protected $indexablePagePartsService;
70
71
    /** @var DomainConfigurationInterface */
72
    protected $domainConfiguration;
73
74
    /** @var array */
75
    protected $properties = [];
76
77
    /** @var int */
78
    protected $numberOfShards;
79
80
    /** @var int */
81
    protected $numberOfReplicas;
82
83
    /** @var Node */
84
    protected $currentTopNode = null;
85
86
    /** @var array */
87
    protected $nodeRefs = [];
88
89
    /**
90
     * @param ContainerInterface      $container
91
     * @param SearchProviderInterface $searchProvider
92
     * @param string                  $name
93
     * @param string                  $type
94
     */
95
    public function __construct($container, $searchProvider, $name, $type, $numberOfShards = 1, $numberOfReplicas = 0)
96
    {
97
        $this->container = $container;
98
        $this->indexName = $name;
99
        $this->indexType = $type;
100
        $this->searchProvider = $searchProvider;
101
        $this->domainConfiguration = $this->container->get('kunstmaan_admin.domain_configuration');
102
        $this->locales = $this->domainConfiguration->getBackendLocales();
103
        $this->analyzerLanguages = $this->container->getParameter('analyzer_languages');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->container->getPar...r('analyzer_languages') of type * is incompatible with the declared type array of property $analyzerLanguages.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
104
        $this->em = $this->container->get('doctrine')->getManager();
105
        $this->numberOfShards = $numberOfShards;
106
        $this->numberOfReplicas = $numberOfReplicas;
107
    }
108
109
    /**
110
     * @param AclProviderInterface $aclProvider
111
     */
112
    public function setAclProvider(AclProviderInterface $aclProvider)
113
    {
114
        $this->aclProvider = $aclProvider;
115
    }
116
117
    /**
118
     * @param IndexablePagePartsService $indexablePagePartsService
119
     */
120
    public function setIndexablePagePartsService(IndexablePagePartsService $indexablePagePartsService)
121
    {
122
        $this->indexablePagePartsService = $indexablePagePartsService;
123
    }
124
125
    /**
126
     * @param array $properties
127
     */
128
    public function setDefaultProperties(array $properties)
129
    {
130
        $this->properties = array_merge($this->properties, $properties);
131
    }
132
133
    /**
134
     * @param LoggerInterface $logger
135
     */
136
    public function setLogger(LoggerInterface $logger)
137
    {
138
        $this->logger = $logger;
139
    }
140
141
    /**
142
     * @return array
143
     */
144
    public function getLanguagesNotAnalyzed()
145
    {
146
        $notAnalyzed = [];
147
        foreach ($this->locales as $locale) {
148
            if (preg_match('/[a-z]{2}_?+[a-zA-Z]{2}/', $locale)) {
149
                $locale = strtolower($locale);
150
            }
151
152
            if (false === \array_key_exists($locale, $this->analyzerLanguages)) {
153
                $notAnalyzed[] = $locale;
154
            }
155
        }
156
157
        return $notAnalyzed;
158
    }
159
160
    /**
161
     * Create node index
162
     */
163
    public function createIndex()
164
    {
165
        //create analysis
166
        $analysis = $this->container->get(
167
            'kunstmaan_search.search.factory.analysis'
168
        );
169
170
        foreach ($this->locales as $locale) {
171
            // Multilanguage check
172
            if (preg_match('/[a-z]{2}_?+[a-zA-Z]{2}/', $locale)) {
173
                $locale = strtolower($locale);
174
            }
175
176
            // Build new index
177
            $index = $this->searchProvider->createIndex($this->indexName . '_' . $locale);
178
179
            if (\array_key_exists($locale, $this->analyzerLanguages)) {
180
                $localeAnalysis = clone $analysis;
181
                $language = $this->analyzerLanguages[$locale]['analyzer'];
182
183
                // Create index with analysis
184
                $this->setAnalysis($index, $localeAnalysis->setupLanguage($language));
185
            } else {
186
                $index->create();
187
            }
188
189
            $this->setMapping($index, $locale);
190
        }
191
    }
192
193
    /**
194
     * Populate node index
195
     */
196
    public function populateIndex()
197
    {
198
        $nodeRepository = $this->em->getRepository('KunstmaanNodeBundle:Node');
199
        $nodes = $nodeRepository->getAllTopNodes();
200
201
        foreach ($nodes as $node) {
202
            $this->currentTopNode = $node;
203
            foreach ($this->locales as $lang) {
204
                $this->createNodeDocuments($node, $lang);
205
            }
206
        }
207
208
        if (!empty($this->documents)) {
209
            $this->searchProvider->addDocuments($this->documents);
210
            $this->documents = [];
211
        }
212
    }
213
214
    /**
215
     * Index a node (including its children) - for the specified language only
216
     *
217
     * @param Node   $node
218
     * @param string $lang
219
     */
220
    public function indexNode(Node $node, $lang)
221
    {
222
        $this->createNodeDocuments($node, $lang);
223
224
        if (!empty($this->documents)) {
225
            $this->searchProvider->addDocuments($this->documents);
226
            $this->documents = [];
227
        }
228
    }
229
230
    /**
231
     * Add documents for the node translation (and children) to the index
232
     *
233
     * @param Node   $node
234
     * @param string $lang
235
     */
236
    public function createNodeDocuments(Node $node, $lang)
237
    {
238
        $nodeTranslation = $node->getNodeTranslation($lang, true);
239
        if ($nodeTranslation && $this->indexNodeTranslation($nodeTranslation)) {
240
            $this->indexChildren($node, $lang);
241
        }
242
    }
243
244
    /**
245
     * Index all children of the specified node (only for the specified
246
     * language)
247
     *
248
     * @param Node   $node
249
     * @param string $lang
250
     */
251
    public function indexChildren(Node $node, $lang)
252
    {
253
        foreach ($node->getChildren() as $childNode) {
254
            $this->indexNode($childNode, $lang);
255
        }
256
    }
257
258
    /**
259
     * Index a node translation
260
     *
261
     * @param NodeTranslation $nodeTranslation
262
     * @param bool            $add             Add node immediately to index?
263
     *
264
     * @return bool Return true if the document has been indexed
265
     */
266
    public function indexNodeTranslation(NodeTranslation $nodeTranslation, $add = false)
267
    {
268
        // Retrieve the public NodeVersion
269
        $publicNodeVersion = $nodeTranslation->getPublicNodeVersion();
270
        if (\is_null($publicNodeVersion)) {
271
            return false;
272
        }
273
274
        $refPage = $this->getNodeRefPage($publicNodeVersion);
275
        if ($refPage->isStructureNode()) {
276
            return true;
277
        }
278
279
        // Only index online NodeTranslations
280
        if (!$nodeTranslation->isOnline()) {
281
            return false;
282
        }
283
284
        $node = $nodeTranslation->getNode();
285
        if ($this->isIndexable($refPage)) {
286
            // Retrieve the referenced entity from the public NodeVersion
287
            $page = $publicNodeVersion->getRef($this->em);
288
289
            $this->addPageToIndex($nodeTranslation, $node, $publicNodeVersion, $page);
0 ignored issues
show
Bug introduced by
It seems like $page defined by $publicNodeVersion->getRef($this->em) on line 287 can be null; however, Kunstmaan\NodeSearchBund...ation::addPageToIndex() 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...
290
            if ($add) {
291
                $this->searchProvider->addDocuments($this->documents);
292
                $this->documents = [];
293
            }
294
        }
295
296
        return true; // return true even if the page itself should not be indexed. This makes sure its children are being processed (i.e. structured nodes)
297
    }
298
299
    /**
300
     * Return if the page is indexable - by default all pages are indexable,
301
     * you can override this by implementing the IndexableInterface on your
302
     * page entity and returning false in the isIndexable method.
303
     *
304
     * @param HasNodeInterface $page
305
     *
306
     * @return bool
307
     */
308
    protected function isIndexable(HasNodeInterface $page)
309
    {
310
        return $this->container->get('kunstmaan_node.pages_configuration')->isIndexable($page);
311
    }
312
313
    /**
314
     * Remove the specified node translation from the index
315
     *
316
     * @param NodeTranslation $nodeTranslation
317
     */
318
    public function deleteNodeTranslation(NodeTranslation $nodeTranslation)
319
    {
320
        $uid = 'nodetranslation_' . $nodeTranslation->getId();
321
        $indexName = $this->indexName . '_' . $nodeTranslation->getLang();
322
        $this->searchProvider->deleteDocument($indexName, $this->indexType, $uid);
323
    }
324
325
    /**
326
     * Delete the specified index
327
     */
328
    public function deleteIndex()
329
    {
330
        foreach ($this->locales as $locale) {
331
            $this->searchProvider->deleteIndex($this->indexName . '_' . $locale);
332
        }
333
    }
334
335
    /**
336
     * Apply the analysis factory to the index
337
     *
338
     * @param Index                    $index
339
     * @param AnalysisFactoryInterface $analysis
340
     */
341
    public function setAnalysis(Index $index, AnalysisFactoryInterface $analysis)
342
    {
343
        $analysers = $analysis->build();
344
        $args = [
345
            'number_of_shards' => $this->numberOfShards,
346
            'number_of_replicas' => $this->numberOfReplicas,
347
            'analysis' => $analysers,
348
        ];
349
350
        if (class_exists(\Elastica\Mapping::class)) {
351
            $args = [
352
                'settings' => [
353
                    'number_of_shards' => $this->numberOfShards,
354
                    'number_of_replicas' => $this->numberOfReplicas,
355
                    'analysis' => $analysers,
356
                ],
357
            ];
358
359
            $ngramDiff = 1;
360
            if (isset($analysers['tokenizer']) && count($analysers['tokenizer']) > 0) {
361
                foreach ($analysers['tokenizer'] as $tokenizer) {
362
                    if ($tokenizer['type'] === 'nGram') {
363
                        $diff = $tokenizer['max_gram'] - $tokenizer['min_gram'];
364
365
                        $ngramDiff = $diff > $ngramDiff ? $diff : $ngramDiff;
366
                    }
367
                }
368
            }
369
370
            if ($ngramDiff > 1) {
371
                $args['settings']['max_ngram_diff'] = $ngramDiff;
372
            }
373
        }
374
375
        $index->create($args);
376
    }
377
378
    /**
379
     * Return default search fields mapping for node translations
380
     *
381
     * @param Index  $index
382
     * @param string $lang
383
     *
384
     * @return Mapping|\Elastica\Mapping
385
     */
386
    protected function createDefaultSearchFieldsMapping(Index $index, $lang = 'en')
387
    {
388
        if (class_exists(\Elastica\Type\Mapping::class)) {
389
            $mapping = new Mapping();
390
            $mapping->setType($index->getType($this->indexType));
0 ignored issues
show
Bug introduced by
The method getType() does not seem to exist on object<Elastica\Index>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
391
        } else {
392
            $mapping = new \Elastica\Mapping();
393
        }
394
395
        $mapping->setProperties($this->properties);
396
397
        return $mapping;
398
    }
399
400
    /**
401
     * Initialize the index with the default search fields mapping
402
     *
403
     * @param Index  $index
404
     * @param string $lang
405
     */
406
    protected function setMapping(Index $index, $lang = 'en')
407
    {
408
        $mapping = $this->createDefaultSearchFieldsMapping($index, $lang);
409
        if (class_exists(\Elastica\Mapping::class)) {
410
            $mapping->send($index);
411
        } else {
412
            $mapping->send();
0 ignored issues
show
Bug introduced by
The call to send() misses a required argument $index.

This check looks for function calls that miss required arguments.

Loading history...
413
        }
414
        $index->refresh();
415
    }
416
417
    /**
418
     * Create a search document for a page
419
     *
420
     * @param NodeTranslation  $nodeTranslation
421
     * @param Node             $node
422
     * @param NodeVersion      $publicNodeVersion
423
     * @param HasNodeInterface $page
424
     */
425
    protected function addPageToIndex(
426
        NodeTranslation $nodeTranslation,
427
        Node $node,
428
        NodeVersion $publicNodeVersion,
429
        HasNodeInterface $page
430
    ) {
431
        $rootNode = $this->currentTopNode;
432
        if (!$rootNode) {
433
            // Fetch main parent of current node...
434
            $rootNode = $this->em->getRepository('KunstmaanNodeBundle:Node')->getRootNodeFor(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\Persistence\ObjectRepository as the method getRootNodeFor() does only exist in the following implementations of said interface: Kunstmaan\NodeBundle\Repository\NodeRepository.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
435
                $node,
436
                $nodeTranslation->getLang()
437
            );
438
        }
439
440
        $doc = array(
441
            'root_id' => $rootNode->getId(),
442
            'node_id' => $node->getId(),
443
            'node_translation_id' => $nodeTranslation->getId(),
444
            'node_version_id' => $publicNodeVersion->getId(),
445
            'title' => $nodeTranslation->getTitle(),
446
            'slug' => $nodeTranslation->getFullSlug(),
447
            'page_class' => ClassLookup::getClass($page),
448
            'created' => $this->getUTCDateTime(
449
                $nodeTranslation->getCreated()
450
            )->format(\DateTime::ISO8601),
451
            'updated' => $this->getUTCDateTime(
452
                $nodeTranslation->getUpdated()
453
            )->format(\DateTime::ISO8601),
454
        );
455
        if ($this->logger) {
456
            $this->logger->info('Indexing document : ' . implode(', ', $doc));
457
        }
458
459
        // Permissions
460
        $this->addPermissions($node, $doc);
461
462
        // Search type
463
        $this->addSearchType($page, $doc);
464
465
        // Parent and Ancestors
466
        $this->addParentAndAncestors($node, $doc);
467
468
        // Content
469
        $this->addPageContent($nodeTranslation, $page, $doc);
470
471
        // Add document to index
472
        $uid = 'nodetranslation_' . $nodeTranslation->getId();
473
474
        $this->addCustomData($page, $doc);
475
476
        $this->documents[] = $this->searchProvider->createDocument(
477
            $uid,
478
            $doc,
0 ignored issues
show
Documentation introduced by
$doc is of type array, 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...
479
            $this->indexName . '_' . $nodeTranslation->getLang(),
480
            $this->indexType
481
        );
482
    }
483
484
    /**
485
     * Add view permissions to the index document
486
     *
487
     * @param Node  $node
488
     * @param array $doc
489
     *
490
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
491
     */
492
    protected function addPermissions(Node $node, &$doc)
493
    {
494
        if (!\is_null($this->aclProvider)) {
495
            $roles = $this->getAclPermissions($node);
496
        } else {
497
            // Fallback when no ACL available / assume everything is accessible...
498
            $roles = array('IS_AUTHENTICATED_ANONYMOUSLY');
499
        }
500
        $doc['view_roles'] = $roles;
501
    }
502
503
    /**
504
     * Add type to the index document
505
     *
506
     * @param object $page
507
     * @param array  $doc
508
     *
509
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
510
     */
511
    protected function addSearchType($page, &$doc)
512
    {
513
        $doc['type'] = $this->container->get('kunstmaan_node.pages_configuration')->getSearchType($page);
514
    }
515
516
    /**
517
     * Add parent nodes to the index document
518
     *
519
     * @param Node  $node
520
     * @param array $doc
521
     *
522
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
523
     */
524
    protected function addParentAndAncestors($node, &$doc)
525
    {
526
        $parent = $node->getParent();
527
528
        if ($parent) {
529
            $doc['parent'] = $parent->getId();
530
            $ancestors = [];
531
            do {
532
                $ancestors[] = $parent->getId();
533
                $parent = $parent->getParent();
534
            } while ($parent);
535
            $doc['ancestors'] = $ancestors;
536
        }
537
    }
538
539
    /**
540
     * Add page content to the index document
541
     *
542
     * @param NodeTranslation  $nodeTranslation
543
     * @param HasNodeInterface $page
544
     * @param array            $doc
545
     */
546
    protected function addPageContent(NodeTranslation $nodeTranslation, $page, &$doc)
547
    {
548
        $this->enterRequestScope($nodeTranslation->getLang());
549
        if ($this->logger) {
550
            $this->logger->debug(
551
                sprintf(
552
                    'Indexing page "%s" / lang : %s / type : %s / id : %d / node id : %d',
553
                    $page->getTitle(),
554
                    $nodeTranslation->getLang(),
555
                    \get_class($page),
556
                    $page->getId(),
557
                    $nodeTranslation->getNode()->getId()
558
                )
559
            );
560
        }
561
562
        $renderer = $this->container->get('templating');
563
        $doc['content'] = '';
564
565
        if ($page instanceof SearchViewTemplateInterface) {
566
            $doc['content'] = $this->renderCustomSearchView($nodeTranslation, $page, $renderer);
0 ignored issues
show
Documentation introduced by
$renderer is of type object|null, but the function expects a object<Symfony\Component...lating\EngineInterface>.

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...
567
568
            return null;
569
        }
570
571
        if ($page instanceof HasPagePartsInterface) {
572
            $doc['content'] = $this->renderDefaultSearchView($nodeTranslation, $page, $renderer);
0 ignored issues
show
Documentation introduced by
$renderer is of type object|null, but the function expects a object<Symfony\Component...lating\EngineInterface>.

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...
573
574
            return null;
575
        }
576
    }
577
578
    /**
579
     * Enter request scope if it is not active yet...
580
     *
581
     * @param string $lang
582
     */
583
    protected function enterRequestScope($lang)
584
    {
585
        $locale = null;
586
        $requestStack = $this->container->get('request_stack');
587
        // If there already is a request, get the locale from it.
588
        if ($requestStack->getCurrentRequest()) {
589
            $locale = $requestStack->getCurrentRequest()->getLocale();
590
        }
591
        // If we don't have a request or the current request locale is different from the node langauge
592
        if (!$requestStack->getCurrentRequest() || ($locale && $locale !== $lang)) {
593
            $request = new Request();
594
            $request->setLocale($lang);
595
596
            $context = $this->container->get('router')->getContext();
597
            $context->setParameter('_locale', $lang);
598
599
            $requestStack->push($request);
600
        }
601
    }
602
603
    /**
604
     * Render a custom search view
605
     *
606
     * @param NodeTranslation             $nodeTranslation
607
     * @param SearchViewTemplateInterface $page
608
     * @param EngineInterface             $renderer
609
     *
610
     * @return string
611
     */
612
    protected function renderCustomSearchView(
613
        NodeTranslation $nodeTranslation,
614
        SearchViewTemplateInterface $page,
615
        EngineInterface $renderer
616
    ) {
617
        $view = $page->getSearchView();
618
        $renderContext = new RenderContext([
619
            'locale' => $nodeTranslation->getLang(),
620
            'page' => $page,
621
            'indexMode' => true,
622
            'nodetranslation' => $nodeTranslation,
623
        ]);
624
625
        if ($page instanceof PageInterface) {
626
            $request = $this->container->get('request_stack')->getCurrentRequest();
627
            $page->service($this->container, $request, $renderContext);
628
        }
629
630
        $content = $this->removeHtml(
631
            $renderer->render(
632
                $view,
633
                $renderContext->getArrayCopy()
634
            )
635
        );
636
637
        return $content;
638
    }
639
640
    /**
641
     * Render default search view (all indexable pageparts in the main context
642
     * of the page)
643
     *
644
     * @param NodeTranslation       $nodeTranslation
645
     * @param HasPagePartsInterface $page
646
     * @param EngineInterface       $renderer
647
     *
648
     * @return string
649
     */
650
    protected function renderDefaultSearchView(
651
        NodeTranslation $nodeTranslation,
652
        HasPagePartsInterface $page,
653
        EngineInterface $renderer
654
    ) {
655
        $pageparts = $this->indexablePagePartsService->getIndexablePageParts($page);
656
        $view = '@KunstmaanNodeSearch/PagePart/view.html.twig';
657
        $content = $this->removeHtml(
658
            $renderer->render(
659
                $view,
660
                array(
661
                    'locale' => $nodeTranslation->getLang(),
662
                    'page' => $page,
663
                    'pageparts' => $pageparts,
664
                    'indexMode' => true,
665
                )
666
            )
667
        );
668
669
        return $content;
670
    }
671
672
    /**
673
     * Add custom data to index document (you can override to add custom fields
674
     * to the search index)
675
     *
676
     * @param HasNodeInterface $page
677
     * @param array            $doc
678
     */
679
    protected function addCustomData(HasNodeInterface $page, &$doc)
680
    {
681
        $event = new IndexNodeEvent($page, $doc);
682
        $this->container->get('event_dispatcher')->dispatch(IndexNodeEvent::EVENT_INDEX_NODE, $event);
683
684
        $doc = $event->doc;
685
686
        if ($page instanceof HasCustomSearchDataInterface) {
687
            $doc += $page->getCustomSearchData($doc);
688
        }
689
    }
690
691
    /**
692
     * Convert a DateTime to UTC equivalent...
693
     *
694
     * @param \DateTime $dateTime
695
     *
696
     * @return \DateTime
697
     */
698
    protected function getUTCDateTime(\DateTime $dateTime)
699
    {
700
        $result = clone $dateTime;
701
        $result->setTimezone(new \DateTimeZone('UTC'));
702
703
        return $result;
704
    }
705
706
    /**
707
     * Removes all HTML markup & decode HTML entities
708
     *
709
     * @param $text
710
     *
711
     * @return string
712
     */
713
    protected function removeHtml($text)
714
    {
715
        if (!trim($text)) {
716
            return '';
717
        }
718
719
        // Remove Styles and Scripts
720
        $crawler = new Crawler();
721
        $crawler->addHtmlContent($text);
722
        $crawler->filter('style, script')->each(function (Crawler $crawler) {
723
            foreach ($crawler as $node) {
724
                $node->parentNode->removeChild($node);
725
            }
726
        });
727
        $text = $crawler->html();
728
729
        // Remove HTML markup
730
        $result = strip_tags($text);
731
732
        // Decode HTML entities
733
        $result = trim(html_entity_decode($result, ENT_QUOTES));
734
735
        return $result;
736
    }
737
738
    /**
739
     * Fetch ACL permissions for the specified entity
740
     *
741
     * @param object $object
742
     *
743
     * @return array
744
     */
745
    protected function getAclPermissions($object)
746
    {
747
        $roles = [];
748
749
        try {
750
            $objectIdentity = ObjectIdentity::fromDomainObject($object);
751
752
            /* @var AclInterface $acl */
753
            $acl = $this->aclProvider->findAcl($objectIdentity);
754
            $objectAces = $acl->getObjectAces();
755
756
            /* @var AuditableEntryInterface $ace */
757 View Code Duplication
            foreach ($objectAces as $ace) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
758
                $securityIdentity = $ace->getSecurityIdentity();
759
                if (
760
                    $securityIdentity instanceof RoleSecurityIdentity &&
761
                    ($ace->getMask() & MaskBuilder::MASK_VIEW != 0)
762
                ) {
763
                    $roles[] = $securityIdentity->getRole();
764
                }
765
            }
766
        } catch (AclNotFoundException $e) {
767
            // No ACL found... assume default
768
            $roles = array('IS_AUTHENTICATED_ANONYMOUSLY');
769
        }
770
771
        return $roles;
772
    }
773
774
    /**
775
     * @param $publicNodeVersion
776
     *
777
     * @return mixed
778
     */
779
    private function getNodeRefPage(NodeVersion $publicNodeVersion)
780
    {
781
        $refEntityName = $publicNodeVersion->getRefEntityName();
782
783
        if (!isset($this->nodeRefs[$refEntityName])) {
784
            $this->nodeRefs[$refEntityName] = new $refEntityName();
785
        }
786
787
        return $this->nodeRefs[$refEntityName];
788
    }
789
}
790