Indexer   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 302
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 1
Bugs 1 Features 1
Metric Value
wmc 33
lcom 1
cbo 8
dl 0
loc 302
rs 9.3999
c 1
b 1
f 1

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A getEntitiesListAliases() 0 4 1
A getEntityAliases() 0 4 1
A getEntityAlias() 0 4 1
A getAllowedEntitiesListAliases() 0 4 1
B getSimpleSearchQuery() 0 29 6
A simpleSearch() 0 6 1
A select() 0 9 1
A query() 0 10 2
A advancedSearch() 0 15 1
A setIsAllowedApplyAcl() 0 4 1
A prepareQuery() 0 7 2
D applyModesBehavior() 0 44 10
A filterAllowedEntities() 0 14 4
1
<?php
2
3
namespace Oro\Bundle\SearchBundle\Engine;
4
5
use Doctrine\Common\Persistence\ObjectManager;
6
7
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
8
9
use Oro\Bundle\SearchBundle\Query\Expression\Lexer;
10
use Oro\Bundle\SearchBundle\Query\Expression\Parser as ExpressionParser;
11
use Oro\Bundle\SearchBundle\Query\Mode;
12
use Oro\Bundle\SearchBundle\Query\Query;
13
use Oro\Bundle\SearchBundle\Query\Result;
14
use Oro\Bundle\SearchBundle\Security\SecurityProvider;
15
16
use Oro\Bundle\SecurityBundle\Search\AclHelper;
17
18
/**
19
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
20
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
21
 * @SuppressWarnings(PHPMD.TooManyMethods)
22
 */
23
class Indexer
24
{
25
    const TEXT_ALL_DATA_FIELD   = 'all_text';
26
27
    const RELATION_ONE_TO_ONE   = 'one-to-one';
28
    const RELATION_MANY_TO_MANY = 'many-to-many';
29
    const RELATION_MANY_TO_ONE  = 'many-to-one';
30
    const RELATION_ONE_TO_MANY  = 'one-to-many';
31
32
    const SEARCH_ENTITY_PERMISSION = 'VIEW';
33
34
    /** @var EngineInterface */
35
    protected $engine;
36
37
    /** @var ObjectManager */
38
    protected $em;
39
40
    /** @var ObjectMapper */
41
    protected $mapper;
42
43
    /** @var SecurityProvider */
44
    protected $securityProvider;
45
46
    /** @var AclHelper */
47
    protected $searchAclHelper;
48
49
    /** @var bool */
50
    protected $isAllowedApplyAcl = true;
51
52
    /**
53
     * @param ObjectManager       $em
54
     * @param EngineInterface     $engine
55
     * @param ObjectMapper        $mapper
56
     * @param SecurityProvider    $securityProvider
57
     * @param AclHelper           $searchAclHelper
58
     * @param EventDispatcherInterface $dispatcher
59
     */
60
    public function __construct(
61
        ObjectManager            $em,
62
        EngineInterface          $engine,
63
        ObjectMapper             $mapper,
64
        SecurityProvider         $securityProvider,
65
        AclHelper                $searchAclHelper,
66
        EventDispatcherInterface $dispatcher
67
    ) {
68
        $this->em               = $em;
69
        $this->engine           = $engine;
70
        $this->mapper           = $mapper;
71
        $this->securityProvider = $securityProvider;
72
        $this->searchAclHelper  = $searchAclHelper;
73
        $this->dispatcher       = $dispatcher;
0 ignored issues
show
Bug introduced by
The property dispatcher does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
74
    }
75
76
    /**
77
     * Get array with mapped entities
78
     *
79
     * @return array
80
     */
81
    public function getEntitiesListAliases()
82
    {
83
        return $this->mapper->getEntitiesListAliases();
84
    }
85
86
    /**
87
     * Gets search aliases for entities
88
     *
89
     * @param string[] $classNames The list of entity FQCN
90
     *
91
     * @return array [entity class name => entity search alias, ...]
92
     *
93
     * @throws \InvalidArgumentException if some of requested entities is not registered in the search index
94
     *                                   or has no the search alias
95
     */
96
    public function getEntityAliases(array $classNames = [])
97
    {
98
        return $this->mapper->getEntityAliases($classNames);
99
    }
100
101
    /**
102
     * Gets the search alias of a given entity
103
     *
104
     * @param string $className The FQCN of an entity
105
     *
106
     * @return string|null The search alias of the entity
107
     *                     or NULL if the entity is not registered in a search index or has no the search alias
108
     */
109
    public function getEntityAlias($className)
110
    {
111
        return $this->mapper->getEntityAlias($className);
112
    }
113
114
    /**
115
     * Get list of entities allowed to user
116
     *
117
     * @return array
118
     */
119
    public function getAllowedEntitiesListAliases()
120
    {
121
        return $this->filterAllowedEntities(self::SEARCH_ENTITY_PERMISSION, $this->getEntitiesListAliases());
122
    }
123
124
    /**
125
     * @param  string  $searchString
126
     * @param  integer $offset
127
     * @param  integer $maxResults
128
     * @param  string  $from
129
     * @param  integer $page
130
     *
131
     * @return Query
132
     */
133
    public function getSimpleSearchQuery($searchString, $offset = 0, $maxResults = 0, $from = null, $page = 0)
134
    {
135
        $searchString = trim($searchString);
136
        $query        = $this->select();
137
138
        if ($from) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $from of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
139
            $query->from($from);
140
        } else {
141
            $query->from('*');
142
        }
143
144
        if ($searchString) {
145
            $query->andWhere(self::TEXT_ALL_DATA_FIELD, Query::OPERATOR_CONTAINS, $searchString, Query::TYPE_TEXT);
146
        }
147
148
        if ($maxResults > 0) {
149
            $query->setMaxResults($maxResults);
150
        } else {
151
            $query->setMaxResults(Query::INFINITY);
152
        }
153
154
        if ($page > 0) {
155
            $query->setFirstResult($maxResults * ($page - 1));
156
        } elseif ($offset > 0) {
157
            $query->setFirstResult($offset);
158
        }
159
160
        return $query;
161
    }
162
163
    /**
164
     * @param  string  $searchString
165
     * @param  integer $offset
166
     * @param  integer $maxResults
167
     * @param  string  $from
168
     * @param  integer $page
169
     * @return Result
170
     */
171
    public function simpleSearch($searchString, $offset = 0, $maxResults = 0, $from = null, $page = 0)
172
    {
173
        $query = $this->getSimpleSearchQuery($searchString, $offset, $maxResults, $from, $page);
174
175
        return $this->query($query);
176
    }
177
178
    /**
179
     * Get query builder with select instance
180
     *
181
     * @return Query
182
     */
183
    public function select()
184
    {
185
        $query = new Query(Query::SELECT);
186
187
        $query->setMappingConfig($this->mapper->getMappingConfig());
188
        $query->setEntityManager($this->em);
189
190
        return $query;
191
    }
192
193
    /**
194
     * Run query with query builder
195
     *
196
     * @param  Query $query
197
     * @return Result
198
     */
199
    public function query(Query $query)
200
    {
201
        $this->prepareQuery($query);
202
        // we haven't allowed entities, so return null search result
203
        if (count($query->getFrom()) == 0) {
204
            return new Result($query, [], 0);
205
        }
206
207
        return $this->engine->search($query);
208
    }
209
210
    /**
211
     * Advanced search from API
212
     *
213
     * @param  string $expression
214
     * @return Result
215
     */
216
    public function advancedSearch($expression)
217
    {
218
        $lexer  = new Lexer();
219
        $parser = new ExpressionParser();
220
221
        /** @var Query $query */
222
        $query = $parser->parse($lexer->tokenize($expression));
223
224
        $query->setMappingConfig($this->mapper->getMappingConfig());
225
226
        /** @var Result $result */
227
        $result = $this->query($query);
228
229
        return $result;
230
    }
231
232
    /**
233
     * @param bool $value
234
     */
235
    public function setIsAllowedApplyAcl($value)
236
    {
237
        $this->isAllowedApplyAcl = (bool)$value;
238
    }
239
240
    /**
241
     * Do query manipulations such as ACL apply etc.
242
     *
243
     * @param Query $query
244
     */
245
    protected function prepareQuery(Query $query)
246
    {
247
        $this->applyModesBehavior($query);
248
        if ($this->isAllowedApplyAcl) {
249
            $this->searchAclHelper->apply($query);
250
        }
251
    }
252
253
    /**
254
     * Apply special behavior of class inheritance processing
255
     *
256
     * @param Query $query
257
     */
258
    protected function applyModesBehavior(Query $query)
259
    {
260
        // process abstract indexes
261
        // make hashes increasing performance
262
        $fromParts   = (array) $query->getFrom();
263
        $fromHash    = array_combine($fromParts, $fromParts);
264
        $aliases     = $this->mapper->getEntitiesListAliases();
265
        $aliasesHash = array_flip($aliases);
266
267
        if (!isset($fromHash['*'])) {
268
            foreach ($fromParts as $part) {
269
                $entityName = $part;
270
                $isAlias    = false;
271
                if (isset($aliasesHash[$part])) {
272
                    // find real name by alias
273
                    $entityName = $aliasesHash[$part];
274
                    $isAlias    = true;
275
                }
276
277
                $mode        = $this->mapper->getEntityModeConfig($entityName);
278
                $descendants = $this->mapper->getRegisteredDescendants($entityName);
279
                if (false !== $descendants) {
280
                    // add descendants to from clause
281
                    foreach ($descendants as $fromPart) {
282
                        if ($isAlias) {
283
                            $fromPart = $aliases[$fromPart];
284
                        }
285
                        if (!isset($fromHash[$fromPart])) {
286
                            $fromHash[$fromPart] = $fromPart;
287
                        }
288
                    }
289
                }
290
291
                if ($mode === Mode::ONLY_DESCENDANTS) {
292
                    unset($fromHash[$part]);
293
                }
294
            }
295
        }
296
297
        $collectedParts = array_values($fromHash);
298
        if ($collectedParts !== $fromParts) {
299
            $query->from($collectedParts);
300
        }
301
    }
302
303
    /**
304
     * Filter array of entities. Return array of allowed entities
305
     *
306
     * @param  string   $attribute Permission
307
     * @param  string[] $entities  The list of entity class names to be checked
308
     * @return string[]
309
     */
310
    protected function filterAllowedEntities($attribute, $entities)
311
    {
312
        foreach (array_keys($entities) as $entityClass) {
313
            $objectString = 'Entity:' . $entityClass;
314
315
            if ($this->securityProvider->isProtectedEntity($entityClass)
316
                && !$this->securityProvider->isGranted($attribute, $objectString)
317
            ) {
318
                unset($entities[$entityClass]);
319
            }
320
        }
321
322
        return $entities;
323
    }
324
}
325