Completed
Pull Request — master (#2657)
by Jeroen
05:52
created

NodePagesConfiguration::getEventDispatcher()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
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\Event\Events;
16
use Kunstmaan\NodeBundle\Event\PageRenderEvent;
17
use Kunstmaan\NodeBundle\Helper\RenderContext;
18
use Kunstmaan\NodeSearchBundle\Event\IndexNodeEvent;
19
use Kunstmaan\NodeSearchBundle\Helper\IndexablePagePartsService;
20
use Kunstmaan\NodeSearchBundle\Helper\SearchViewTemplateInterface;
21
use Kunstmaan\PagePartBundle\Helper\HasPagePartsInterface;
22
use Kunstmaan\SearchBundle\Configuration\SearchConfigurationInterface;
23
use Kunstmaan\SearchBundle\Provider\SearchProviderInterface;
24
use Kunstmaan\SearchBundle\Search\AnalysisFactoryInterface;
25
use Kunstmaan\UtilitiesBundle\Helper\ClassLookup;
26
use Psr\Log\LoggerInterface;
27
use Symfony\Component\DependencyInjection\ContainerInterface;
28
use Symfony\Component\DomCrawler\Crawler;
29
use Symfony\Component\HttpFoundation\Request;
30
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
31
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
32
use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
33
use Symfony\Component\Security\Acl\Model\AclInterface;
34
use Symfony\Component\Security\Acl\Model\AclProviderInterface;
35
use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
36
use Symfony\Component\Templating\EngineInterface;
37
38
class NodePagesConfiguration implements SearchConfigurationInterface
39
{
40
    /** @var string */
41
    protected $indexName;
42
43
    /** @var string */
44
    protected $indexType;
45
46
    /** @var SearchProviderInterface */
47
    protected $searchProvider;
48
49
    /** @var array */
50
    protected $locales = [];
51
52
    /** @var array */
53
    protected $analyzerLanguages;
54
55
    /** @var EntityManager */
56
    protected $em;
57
58
    /** @var array */
59
    protected $documents = [];
60
61
    /** @var ContainerInterface */
62
    protected $container;
63
64
    /** @var AclProviderInterface */
65
    protected $aclProvider = null;
66
67
    /** @var LoggerInterface */
68
    protected $logger = null;
69
70
    /** @var IndexablePagePartsService */
71
    protected $indexablePagePartsService;
72
73
    /** @var DomainConfigurationInterface */
74
    protected $domainConfiguration;
75
76
    /** @var array */
77
    protected $properties = [];
78
79
    /** @var int */
80
    protected $numberOfShards;
81
82
    /** @var int */
83
    protected $numberOfReplicas;
84
85
    /** @var Node */
86
    protected $currentTopNode = null;
87
88
    /** @var array */
89
    protected $nodeRefs = [];
90
91
    /**
92
     * @param ContainerInterface      $container
93
     * @param SearchProviderInterface $searchProvider
94
     * @param string                  $name
95
     * @param string                  $type
96
     */
97
    public function __construct($container, $searchProvider, $name, $type, $numberOfShards = 1, $numberOfReplicas = 0)
98
    {
99
        $this->container = $container;
100
        $this->indexName = $name;
101
        $this->indexType = $type;
102
        $this->searchProvider = $searchProvider;
103
        $this->domainConfiguration = $this->container->get('kunstmaan_admin.domain_configuration');
104
        $this->locales = $this->domainConfiguration->getBackendLocales();
105
        $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...
106
        $this->em = $this->container->get('doctrine')->getManager();
107
        $this->numberOfShards = $numberOfShards;
108
        $this->numberOfReplicas = $numberOfReplicas;
109
    }
110
111
    /**
112
     * @param AclProviderInterface $aclProvider
113
     */
114
    public function setAclProvider(AclProviderInterface $aclProvider)
115
    {
116
        $this->aclProvider = $aclProvider;
117
    }
118
119
    /**
120
     * @param IndexablePagePartsService $indexablePagePartsService
121
     */
122
    public function setIndexablePagePartsService(IndexablePagePartsService $indexablePagePartsService)
123
    {
124
        $this->indexablePagePartsService = $indexablePagePartsService;
125
    }
126
127
    /**
128
     * @param array $properties
129
     */
130
    public function setDefaultProperties(array $properties)
131
    {
132
        $this->properties = array_merge($this->properties, $properties);
133
    }
134
135
    /**
136
     * @param LoggerInterface $logger
137
     */
138
    public function setLogger(LoggerInterface $logger)
139
    {
140
        $this->logger = $logger;
141
    }
142
143
    /**
144
     * @return array
145
     */
146
    public function getLanguagesNotAnalyzed()
147
    {
148
        $notAnalyzed = [];
149
        foreach ($this->locales as $locale) {
150
            if (preg_match('/[a-z]{2}_?+[a-zA-Z]{2}/', $locale)) {
151
                $locale = strtolower($locale);
152
            }
153
154
            if (false === \array_key_exists($locale, $this->analyzerLanguages)) {
155
                $notAnalyzed[] = $locale;
156
            }
157
        }
158
159
        return $notAnalyzed;
160
    }
161
162
    /**
163
     * Create node index
164
     */
165
    public function createIndex()
166
    {
167
        //create analysis
168
        $analysis = $this->container->get(
169
            'kunstmaan_search.search.factory.analysis'
170
        );
171
172
        foreach ($this->locales as $locale) {
173
            // Multilanguage check
174
            if (preg_match('/[a-z]{2}_?+[a-zA-Z]{2}/', $locale)) {
175
                $locale = strtolower($locale);
176
            }
177
178
            // Build new index
179
            $index = $this->searchProvider->createIndex($this->indexName . '_' . $locale);
180
181
            if (\array_key_exists($locale, $this->analyzerLanguages)) {
182
                $localeAnalysis = clone $analysis;
183
                $language = $this->analyzerLanguages[$locale]['analyzer'];
184
185
                // Create index with analysis
186
                $this->setAnalysis($index, $localeAnalysis->setupLanguage($language));
187
            } else {
188
                $index->create();
189
            }
190
191
            $this->setMapping($index, $locale);
192
        }
193
    }
194
195
    /**
196
     * Populate node index
197
     */
198
    public function populateIndex()
199
    {
200
        $nodeRepository = $this->em->getRepository(Node::class);
201
        $nodes = $nodeRepository->getAllTopNodes();
202
203
        foreach ($nodes as $node) {
204
            $this->currentTopNode = $node;
205
            foreach ($this->locales as $lang) {
206
                $this->createNodeDocuments($node, $lang);
207
            }
208
        }
209
210
        if (!empty($this->documents)) {
211
            $this->searchProvider->addDocuments($this->documents);
212
            $this->documents = [];
213
        }
214
    }
215
216
    /**
217
     * Index a node (including its children) - for the specified language only
218
     *
219
     * @param Node   $node
220
     * @param string $lang
221
     */
222
    public function indexNode(Node $node, $lang)
223
    {
224
        $this->createNodeDocuments($node, $lang);
225
226
        if (!empty($this->documents)) {
227
            $this->searchProvider->addDocuments($this->documents);
228
            $this->documents = [];
229
        }
230
    }
231
232
    /**
233
     * Add documents for the node translation (and children) to the index
234
     *
235
     * @param Node   $node
236
     * @param string $lang
237
     */
238
    public function createNodeDocuments(Node $node, $lang)
239
    {
240
        $nodeTranslation = $node->getNodeTranslation($lang, true);
241
        if ($nodeTranslation && $this->indexNodeTranslation($nodeTranslation)) {
242
            $this->indexChildren($node, $lang);
243
        }
244
    }
245
246
    /**
247
     * Index all children of the specified node (only for the specified
248
     * language)
249
     *
250
     * @param Node   $node
251
     * @param string $lang
252
     */
253
    public function indexChildren(Node $node, $lang)
254
    {
255
        foreach ($node->getChildren() as $childNode) {
256
            $this->indexNode($childNode, $lang);
257
        }
258
    }
259
260
    /**
261
     * Index a node translation
262
     *
263
     * @param NodeTranslation $nodeTranslation
264
     * @param bool            $add             Add node immediately to index?
265
     *
266
     * @return bool Return true if the document has been indexed
267
     */
268
    public function indexNodeTranslation(NodeTranslation $nodeTranslation, $add = false)
269
    {
270
        // Retrieve the public NodeVersion
271
        $publicNodeVersion = $nodeTranslation->getPublicNodeVersion();
272
        if (\is_null($publicNodeVersion)) {
273
            return false;
274
        }
275
276
        $refPage = $this->getNodeRefPage($publicNodeVersion);
277
        if ($refPage->isStructureNode()) {
278
            return true;
279
        }
280
281
        // Only index online NodeTranslations
282
        if (!$nodeTranslation->isOnline()) {
283
            return false;
284
        }
285
286
        $node = $nodeTranslation->getNode();
287
        if ($this->isIndexable($refPage)) {
288
            // Retrieve the referenced entity from the public NodeVersion
289
            $page = $publicNodeVersion->getRef($this->em);
290
291
            $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 289 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...
292
            if ($add) {
293
                $this->searchProvider->addDocuments($this->documents);
294
                $this->documents = [];
295
            }
296
        }
297
298
        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)
299
    }
300
301
    /**
302
     * Return if the page is indexable - by default all pages are indexable,
303
     * you can override this by implementing the IndexableInterface on your
304
     * page entity and returning false in the isIndexable method.
305
     *
306
     * @param HasNodeInterface $page
307
     *
308
     * @return bool
309
     */
310
    protected function isIndexable(HasNodeInterface $page)
311
    {
312
        return $this->container->get('kunstmaan_node.pages_configuration')->isIndexable($page);
313
    }
314
315
    /**
316
     * Remove the specified node translation from the index
317
     *
318
     * @param NodeTranslation $nodeTranslation
319
     */
320
    public function deleteNodeTranslation(NodeTranslation $nodeTranslation)
321
    {
322
        $uid = 'nodetranslation_' . $nodeTranslation->getId();
323
        $indexName = $this->indexName . '_' . $nodeTranslation->getLang();
324
        $this->searchProvider->deleteDocument($indexName, $this->indexType, $uid);
325
    }
326
327
    /**
328
     * Delete the specified index
329
     */
330
    public function deleteIndex()
331
    {
332
        foreach ($this->locales as $locale) {
333
            $this->searchProvider->deleteIndex($this->indexName . '_' . $locale);
334
        }
335
    }
336
337
    /**
338
     * Apply the analysis factory to the index
339
     *
340
     * @param Index                    $index
341
     * @param AnalysisFactoryInterface $analysis
342
     */
343
    public function setAnalysis(Index $index, AnalysisFactoryInterface $analysis)
344
    {
345
        $analysers = $analysis->build();
346
        $args = [
347
            'number_of_shards' => $this->numberOfShards,
348
            'number_of_replicas' => $this->numberOfReplicas,
349
            'analysis' => $analysers,
350
        ];
351
352
        if (class_exists(\Elastica\Mapping::class)) {
353
            $args = [
354
                'settings' => [
355
                    'number_of_shards' => $this->numberOfShards,
356
                    'number_of_replicas' => $this->numberOfReplicas,
357
                    'analysis' => $analysers,
358
                ],
359
            ];
360
361
            $ngramDiff = 1;
362
            if (isset($analysers['tokenizer']) && count($analysers['tokenizer']) > 0) {
363
                foreach ($analysers['tokenizer'] as $tokenizer) {
364
                    if ($tokenizer['type'] === 'nGram') {
365
                        $diff = $tokenizer['max_gram'] - $tokenizer['min_gram'];
366
367
                        $ngramDiff = $diff > $ngramDiff ? $diff : $ngramDiff;
368
                    }
369
                }
370
            }
371
372
            if ($ngramDiff > 1) {
373
                $args['settings']['max_ngram_diff'] = $ngramDiff;
374
            }
375
        }
376
377
        $index->create($args);
378
    }
379
380
    /**
381
     * Return default search fields mapping for node translations
382
     *
383
     * @param Index  $index
384
     * @param string $lang
385
     *
386
     * @return Mapping|\Elastica\Mapping
387
     */
388
    protected function createDefaultSearchFieldsMapping(Index $index, $lang = 'en')
389
    {
390
        if (class_exists(\Elastica\Type\Mapping::class)) {
391
            $mapping = new Mapping();
392
            $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...
393
        } else {
394
            $mapping = new \Elastica\Mapping();
395
        }
396
397
        $mapping->setProperties($this->properties);
398
399
        return $mapping;
400
    }
401
402
    /**
403
     * Initialize the index with the default search fields mapping
404
     *
405
     * @param Index  $index
406
     * @param string $lang
407
     */
408
    protected function setMapping(Index $index, $lang = 'en')
409
    {
410
        $mapping = $this->createDefaultSearchFieldsMapping($index, $lang);
411
        if (class_exists(\Elastica\Mapping::class)) {
412
            $mapping->send($index);
413
        } else {
414
            $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...
415
        }
416
        $index->refresh();
417
    }
418
419
    /**
420
     * Create a search document for a page
421
     *
422
     * @param NodeTranslation  $nodeTranslation
423
     * @param Node             $node
424
     * @param NodeVersion      $publicNodeVersion
425
     * @param HasNodeInterface $page
426
     */
427
    protected function addPageToIndex(
428
        NodeTranslation $nodeTranslation,
429
        Node $node,
430
        NodeVersion $publicNodeVersion,
431
        HasNodeInterface $page
432
    ) {
433
        $rootNode = $this->currentTopNode;
434
        if (!$rootNode) {
435
            // Fetch main parent of current node...
436
            $rootNode = $this->em->getRepository(Node::class)->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...
437
                $node,
438
                $nodeTranslation->getLang()
439
            );
440
        }
441
442
        $doc = array(
443
            'root_id' => $rootNode->getId(),
444
            'node_id' => $node->getId(),
445
            'node_translation_id' => $nodeTranslation->getId(),
446
            'node_version_id' => $publicNodeVersion->getId(),
447
            'title' => $nodeTranslation->getTitle(),
448
            'slug' => $nodeTranslation->getFullSlug(),
449
            'page_class' => ClassLookup::getClass($page),
450
            'created' => $this->getUTCDateTime(
451
                $nodeTranslation->getCreated()
452
            )->format(\DateTime::ISO8601),
453
            'updated' => $this->getUTCDateTime(
454
                $nodeTranslation->getUpdated()
455
            )->format(\DateTime::ISO8601),
456
        );
457
        if ($this->logger) {
458
            $this->logger->info('Indexing document : ' . implode(', ', $doc));
459
        }
460
461
        // Permissions
462
        $this->addPermissions($node, $doc);
463
464
        // Search type
465
        $this->addSearchType($page, $doc);
466
467
        // Parent and Ancestors
468
        $this->addParentAndAncestors($node, $doc);
469
470
        // Content
471
        $this->addPageContent($nodeTranslation, $page, $doc);
472
473
        // Add document to index
474
        $uid = 'nodetranslation_' . $nodeTranslation->getId();
475
476
        $this->addCustomData($page, $doc);
477
478
        $this->documents[] = $this->searchProvider->createDocument(
479
            $uid,
480
            $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...
481
            $this->indexName . '_' . $nodeTranslation->getLang(),
482
            $this->indexType
483
        );
484
    }
485
486
    /**
487
     * Add view permissions to the index document
488
     *
489
     * @param Node  $node
490
     * @param array $doc
491
     *
492
     * @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...
493
     */
494
    protected function addPermissions(Node $node, &$doc)
495
    {
496
        if (!\is_null($this->aclProvider)) {
497
            $roles = $this->getAclPermissions($node);
498
        } else {
499
            // Fallback when no ACL available / assume everything is accessible...
500
            $roles = array('IS_AUTHENTICATED_ANONYMOUSLY');
501
        }
502
        $doc['view_roles'] = $roles;
503
    }
504
505
    /**
506
     * Add type to the index document
507
     *
508
     * @param object $page
509
     * @param array  $doc
510
     *
511
     * @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...
512
     */
513
    protected function addSearchType($page, &$doc)
514
    {
515
        $doc['type'] = $this->container->get('kunstmaan_node.pages_configuration')->getSearchType($page);
516
    }
517
518
    /**
519
     * Add parent nodes to the index document
520
     *
521
     * @param Node  $node
522
     * @param array $doc
523
     *
524
     * @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...
525
     */
526
    protected function addParentAndAncestors($node, &$doc)
527
    {
528
        $parent = $node->getParent();
529
530
        if ($parent) {
531
            $doc['parent'] = $parent->getId();
532
            $ancestors = [];
533
            do {
534
                $ancestors[] = $parent->getId();
535
                $parent = $parent->getParent();
536
            } while ($parent);
537
            $doc['ancestors'] = $ancestors;
538
        }
539
    }
540
541
    /**
542
     * Add page content to the index document
543
     *
544
     * @param NodeTranslation  $nodeTranslation
545
     * @param HasNodeInterface $page
546
     * @param array            $doc
547
     */
548
    protected function addPageContent(NodeTranslation $nodeTranslation, $page, &$doc)
549
    {
550
        $this->enterRequestScope($nodeTranslation->getLang());
551
        if ($this->logger) {
552
            $this->logger->debug(
553
                sprintf(
554
                    'Indexing page "%s" / lang : %s / type : %s / id : %d / node id : %d',
555
                    $page->getTitle(),
556
                    $nodeTranslation->getLang(),
557
                    \get_class($page),
558
                    $page->getId(),
559
                    $nodeTranslation->getNode()->getId()
560
                )
561
            );
562
        }
563
564
        $renderer = $this->container->get('templating');
565
        $doc['content'] = '';
566
567
        if ($page instanceof SearchViewTemplateInterface) {
568
            $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...
569
570
            return null;
571
        }
572
573
        if ($page instanceof HasPagePartsInterface) {
574
            $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...
575
576
            return null;
577
        }
578
    }
579
580
    /**
581
     * Enter request scope if it is not active yet...
582
     *
583
     * @param string $lang
584
     */
585
    protected function enterRequestScope($lang)
586
    {
587
        $locale = null;
588
        $requestStack = $this->container->get('request_stack');
589
        // If there already is a request, get the locale from it.
590
        if ($requestStack->getCurrentRequest()) {
591
            $locale = $requestStack->getCurrentRequest()->getLocale();
592
        }
593
        // If we don't have a request or the current request locale is different from the node langauge
594
        if (!$requestStack->getCurrentRequest() || ($locale && $locale !== $lang)) {
595
            $request = new Request();
596
            $request->setLocale($lang);
597
598
            $context = $this->container->get('router')->getContext();
599
            $context->setParameter('_locale', $lang);
600
601
            $requestStack->push($request);
602
        }
603
    }
604
605
    /**
606
     * Render a custom search view
607
     *
608
     * @param NodeTranslation             $nodeTranslation
609
     * @param SearchViewTemplateInterface $page
610
     * @param EngineInterface             $renderer
611
     *
612
     * @return string
613
     */
614
    protected function renderCustomSearchView(
615
        NodeTranslation $nodeTranslation,
616
        SearchViewTemplateInterface $page,
617
        EngineInterface $renderer
618
    ) {
619
        $view = $page->getSearchView();
620
        $renderContext = new RenderContext([
621
            'locale' => $nodeTranslation->getLang(),
622
            'page' => $page,
623
            'indexMode' => true,
624
            'nodetranslation' => $nodeTranslation,
625
        ]);
626
627
        $request = $this->container->get('request_stack')->getCurrentRequest();
628
        // NEXT_MAJOR: Remove if and `$page->service` call
629
        if ($page instanceof PageInterface) {
630
            $page->service($this->container, $request, $renderContext);
631
        }
632
633
        $pageRenderEvent = new PageRenderEvent($request, $page, $renderContext);
0 ignored issues
show
Documentation introduced by
$page is of type object<Kunstmaan\NodeSea...hViewTemplateInterface>, but the function expects a object<Kunstmaan\NodeBundle\Entity\PageInterface>.

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...
634
        $this->getEventDispatcher()->dispatch(Events::PAGE_RENDER, $pageRenderEvent);
635
636
        $renderContext = clone $pageRenderEvent->getRenderContext();
637
638
        $content = $this->removeHtml(
639
            $renderer->render(
640
                $view,
641
                $renderContext->getArrayCopy()
642
            )
643
        );
644
645
        return $content;
646
    }
647
648
    /**
649
     * Render default search view (all indexable pageparts in the main context
650
     * of the page)
651
     *
652
     * @param NodeTranslation       $nodeTranslation
653
     * @param HasPagePartsInterface $page
654
     * @param EngineInterface       $renderer
655
     *
656
     * @return string
657
     */
658
    protected function renderDefaultSearchView(
659
        NodeTranslation $nodeTranslation,
660
        HasPagePartsInterface $page,
661
        EngineInterface $renderer
662
    ) {
663
        $pageparts = $this->indexablePagePartsService->getIndexablePageParts($page);
664
        $view = '@KunstmaanNodeSearch/PagePart/view.html.twig';
665
        $content = $this->removeHtml(
666
            $renderer->render(
667
                $view,
668
                array(
669
                    'locale' => $nodeTranslation->getLang(),
670
                    'page' => $page,
671
                    'pageparts' => $pageparts,
672
                    'indexMode' => true,
673
                )
674
            )
675
        );
676
677
        return $content;
678
    }
679
680
    /**
681
     * Add custom data to index document (you can override to add custom fields
682
     * to the search index)
683
     *
684
     * @param HasNodeInterface $page
685
     * @param array            $doc
686
     */
687
    protected function addCustomData(HasNodeInterface $page, &$doc)
688
    {
689
        $event = new IndexNodeEvent($page, $doc);
690
        $this->getEventDispatcher()->dispatch(IndexNodeEvent::EVENT_INDEX_NODE, $event);
691
692
        $doc = $event->doc;
693
694
        if ($page instanceof HasCustomSearchDataInterface) {
695
            $doc += $page->getCustomSearchData($doc);
696
        }
697
    }
698
699
    /**
700
     * Convert a DateTime to UTC equivalent...
701
     *
702
     * @param \DateTime $dateTime
703
     *
704
     * @return \DateTime
705
     */
706
    protected function getUTCDateTime(\DateTime $dateTime)
707
    {
708
        $result = clone $dateTime;
709
        $result->setTimezone(new \DateTimeZone('UTC'));
710
711
        return $result;
712
    }
713
714
    /**
715
     * Removes all HTML markup & decode HTML entities
716
     *
717
     * @param $text
718
     *
719
     * @return string
720
     */
721
    protected function removeHtml($text)
722
    {
723
        if (!trim($text)) {
724
            return '';
725
        }
726
727
        // Remove Styles and Scripts
728
        $crawler = new Crawler();
729
        $crawler->addHtmlContent($text);
730
        $crawler->filter('style, script')->each(function (Crawler $crawler) {
731
            foreach ($crawler as $node) {
732
                $node->parentNode->removeChild($node);
733
            }
734
        });
735
        $text = $crawler->html();
736
737
        // Remove HTML markup
738
        $result = strip_tags($text);
739
740
        // Decode HTML entities
741
        $result = trim(html_entity_decode($result, ENT_QUOTES));
742
743
        return $result;
744
    }
745
746
    /**
747
     * Fetch ACL permissions for the specified entity
748
     *
749
     * @param object $object
750
     *
751
     * @return array
752
     */
753
    protected function getAclPermissions($object)
754
    {
755
        $roles = [];
756
757
        try {
758
            $objectIdentity = ObjectIdentity::fromDomainObject($object);
759
760
            /* @var AclInterface $acl */
761
            $acl = $this->aclProvider->findAcl($objectIdentity);
762
            $objectAces = $acl->getObjectAces();
763
764
            /* @var AuditableEntryInterface $ace */
765 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...
766
                $securityIdentity = $ace->getSecurityIdentity();
767
                if (
768
                    $securityIdentity instanceof RoleSecurityIdentity &&
769
                    ($ace->getMask() & MaskBuilder::MASK_VIEW != 0)
770
                ) {
771
                    $roles[] = $securityIdentity->getRole();
772
                }
773
            }
774
        } catch (AclNotFoundException $e) {
775
            // No ACL found... assume default
776
            $roles = array('IS_AUTHENTICATED_ANONYMOUSLY');
777
        }
778
779
        return $roles;
780
    }
781
782
    /**
783
     * @param $publicNodeVersion
784
     *
785
     * @return mixed
786
     */
787
    private function getNodeRefPage(NodeVersion $publicNodeVersion)
788
    {
789
        $refEntityName = $publicNodeVersion->getRefEntityName();
790
791
        if (!isset($this->nodeRefs[$refEntityName])) {
792
            $this->nodeRefs[$refEntityName] = new $refEntityName();
793
        }
794
795
        return $this->nodeRefs[$refEntityName];
796
    }
797
798
    private function getEventDispatcher()
799
    {
800
        return $this->container->get('event_dispatcher');
801
    }
802
}
803