Completed
Push — ezp_31440 ( 87ccef...f4ac1f )
by
unknown
23:26 queued 08:47
created

SearchService::find()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
5
 * @license For full copyright and license information view LICENSE file distributed with this source code.
6
 */
7
declare(strict_types=1);
8
9
namespace eZ\Publish\Core\Repository;
10
11
use eZ\Publish\API\Repository\SearchService as SearchServiceInterface;
12
use eZ\Publish\API\Repository\PermissionCriterionResolver;
13
use eZ\Publish\API\Repository\Values\Content\Content;
14
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
15
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd;
16
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalOperator;
17
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Location as LocationCriterion;
18
use eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location as LocationSortClause;
19
use eZ\Publish\API\Repository\Values\Content\Query;
20
use eZ\Publish\API\Repository\Values\Content\LocationQuery;
21
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
22
use eZ\Publish\API\Repository\Values\Content\Search\SearchResult;
23
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
24
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
25
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType;
26
use eZ\Publish\Core\Repository\Mapper\ContentDomainMapper;
27
use eZ\Publish\SPI\Search\Capable;
28
use eZ\Publish\Core\Search\Common\BackgroundIndexer;
29
use eZ\Publish\SPI\Search\Handler;
30
31
/**
32
 * Search service.
33
 */
34
class SearchService implements SearchServiceInterface
35
{
36
    /** @var \eZ\Publish\Core\Repository\Repository */
37
    protected $repository;
38
39
    /** @var \eZ\Publish\SPI\Search\Handler */
40
    protected $searchHandler;
41
42
    /** @var array */
43
    protected $settings;
44
45
    /** @var \eZ\Publish\Core\Repository\Mapper\ContentDomainMapper */
46
    protected $contentDomainMapper;
47
48
    /** @var \eZ\Publish\API\Repository\PermissionCriterionResolver */
49
    protected $permissionCriterionResolver;
50
51
    /** @var \eZ\Publish\Core\Search\Common\BackgroundIndexer */
52
    protected $backgroundIndexer;
53
54
    /**
55
     * Setups service with reference to repository object that created it & corresponding handler.
56
     *
57
     * @param \eZ\Publish\API\Repository\Repository $repository
58
     * @param \eZ\Publish\SPI\Search\Handler $searchHandler
59
     * @param \eZ\Publish\Core\Repository\Mapper\ContentDomainMapper $contentDomainMapper
60
     * @param \eZ\Publish\API\Repository\PermissionCriterionResolver $permissionCriterionResolver
61
     * @param \eZ\Publish\Core\Search\Common\BackgroundIndexer $backgroundIndexer
62
     * @param array $settings
63
     */
64 View Code Duplication
    public function __construct(
65
        RepositoryInterface $repository,
66
        Handler $searchHandler,
67
        ContentDomainMapper $contentDomainMapper,
68
        PermissionCriterionResolver $permissionCriterionResolver,
69
        BackgroundIndexer $backgroundIndexer,
70
        array $settings = []
71
    ) {
72
        $this->repository = $repository;
0 ignored issues
show
Documentation Bug introduced by
It seems like $repository of type object<eZ\Publish\API\Repository\Repository> is incompatible with the declared type object<eZ\Publish\Core\Repository\Repository> of property $repository.

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...
73
        $this->searchHandler = $searchHandler;
74
        $this->contentDomainMapper = $contentDomainMapper;
75
        // Union makes sure default settings are ignored if provided in argument
76
        $this->settings = $settings + [
77
            //'defaultSetting' => array(),
78
        ];
79
        $this->permissionCriterionResolver = $permissionCriterionResolver;
80
        $this->backgroundIndexer = $backgroundIndexer;
81
    }
82
83
    /**
84
     * Finds content objects for the given query.
85
     *
86
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if query is not valid
87
     *
88
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
89
     * @param array $languageFilter Configuration for specifying prioritized languages query will be performed on.
90
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
91
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations.
92
     * @param bool $filterOnUserPermissions if true only the objects which the user is allowed to read are returned.
93
     *
94
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
95
     */
96
    public function findContent(Query $query, array $languageFilter = [], bool $filterOnUserPermissions = true): SearchResult
97
    {
98
        $result = $this->internalFindContentInfo($query, $languageFilter, $filterOnUserPermissions);
99
        $missingContentList = $this->contentDomainMapper->buildContentDomainObjectsOnSearchResult($result, $languageFilter);
100
        foreach ($missingContentList as $missingContent) {
101
            $this->backgroundIndexer->registerContent($missingContent);
102
        }
103
104
        return $result;
105
    }
106
107
    /**
108
     * Finds contentInfo objects for the given query.
109
     *
110
     * @see SearchServiceInterface::findContentInfo()
111
     *
112
     * @since 5.4.5
113
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if query is not valid
114
     *
115
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
116
     * @param array $languageFilter - a map of filters for the returned fields.
117
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
118
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations.
119
     * @param bool $filterOnUserPermissions if true (default) only the objects which is the user allowed to read are returned.
120
     *
121
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
122
     */
123
    public function findContentInfo(Query $query, array $languageFilter = [], bool $filterOnUserPermissions = true): SearchResult
124
    {
125
        $result = $this->internalFindContentInfo($query, $languageFilter, $filterOnUserPermissions);
126
        foreach ($result->searchHits as $hit) {
127
            $hit->valueObject = $this->contentDomainMapper->buildContentInfoDomainObject(
128
                $hit->valueObject
129
            );
130
        }
131
132
        return $result;
133
    }
134
135
    /**
136
     * Finds SPI content info objects for the given query.
137
     *
138
     * Internal for use by {@link findContent} and {@link findContentInfo}.
139
     *
140
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if query is not valid
141
     *
142
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
143
     * @param array $languageFilter - a map of filters for the returned fields.
144
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
145
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations.
146
     * @param bool $filterOnUserPermissions if true only the objects which is the user allowed to read are returned.
147
     *
148
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult With "raw" SPI contentInfo objects in result
149
     */
150
    protected function internalFindContentInfo(Query $query, array $languageFilter = [], $filterOnUserPermissions = true)
151
    {
152
        if (!is_int($query->offset)) {
153
            throw new InvalidArgumentType(
154
                '$query->offset',
155
                'integer',
156
                $query->offset
157
            );
158
        }
159
160
        if (!is_int($query->limit)) {
161
            throw new InvalidArgumentType(
162
                '$query->limit',
163
                'integer',
164
                $query->limit
165
            );
166
        }
167
168
        $query = clone $query;
169
        $query->filter = $query->filter ?: new Criterion\MatchAll();
170
171
        $this->validateContentCriteria([$query->query], '$query');
172
        $this->validateContentCriteria([$query->filter], '$query');
173
        $this->validateContentSortClauses($query);
174
175 View Code Duplication
        if ($filterOnUserPermissions && !$this->addPermissionsCriterion($query->filter)) {
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...
176
            return new SearchResult(['time' => 0, 'totalCount' => 0]);
177
        }
178
179
        return $this->searchHandler->findContent($query, $languageFilter);
180
    }
181
182
    /**
183
     * Checks that $criteria does not contain Location criterions.
184
     *
185
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
186
     *
187
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion[] $criteria
188
     * @param string $argumentName
189
     */
190
    protected function validateContentCriteria(array $criteria, $argumentName)
191
    {
192
        foreach ($criteria as $criterion) {
193
            if ($criterion instanceof LocationCriterion) {
194
                throw new InvalidArgumentException(
195
                    $argumentName,
196
                    'Location Criteria cannot be used in Content search'
197
                );
198
            }
199
            if ($criterion instanceof LogicalOperator) {
200
                $this->validateContentCriteria($criterion->criteria, $argumentName);
201
            }
202
        }
203
    }
204
205
    /**
206
     * Checks that $query does not contain Location sort clauses.
207
     *
208
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
209
     *
210
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
211
     */
212
    protected function validateContentSortClauses(Query $query)
213
    {
214
        foreach ($query->sortClauses as $sortClause) {
215
            if ($sortClause instanceof LocationSortClause) {
216
                throw new InvalidArgumentException('$query', 'Location Sort Clauses cannot be used in Content search');
217
            }
218
        }
219
    }
220
221
    /**
222
     * Performs a query for a single content object.
223
     *
224
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the object was not found by the query or due to permissions
225
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if criterion is not valid
226
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is more than one result matching the criterions
227
     *
228
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
229
     * @param array $languageFilter Configuration for specifying prioritized languages query will be performed on.
230
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
231
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations.
232
     * @param bool $filterOnUserPermissions if true only the objects which is the user allowed to read are returned.
233
     *
234
     * @return \eZ\Publish\API\Repository\Values\Content\Content
235
     */
236
    public function findSingle(Criterion $filter, array $languageFilter = [], bool $filterOnUserPermissions = true): Content
237
    {
238
        $this->validateContentCriteria([$filter], '$filter');
239
240
        if ($filterOnUserPermissions && !$this->addPermissionsCriterion($filter)) {
241
            throw new NotFoundException('Content', '*');
242
        }
243
244
        $contentInfo = $this->searchHandler->findSingle($filter, $languageFilter);
245
246
        return $this->repository->getContentService()->internalLoadContent(
247
            $contentInfo->id,
248
            (!empty($languageFilter['languages']) ? $languageFilter['languages'] : null),
249
            null,
250
            false,
251
            (isset($languageFilter['useAlwaysAvailable']) ? $languageFilter['useAlwaysAvailable'] : true)
252
        );
253
    }
254
255
    /**
256
     * Suggests a list of values for the given prefix.
257
     *
258
     * @param string $prefix
259
     * @param string[] $fieldPaths
260
     * @param int $limit
261
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
262
     */
263
    public function suggest(string $prefix, array $fieldPaths = [], int $limit = 10, Criterion $filter = null)
264
    {
265
    }
266
267
    /**
268
     * Finds Locations for the given query.
269
     *
270
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if query is not valid
271
     *
272
     * @param \eZ\Publish\API\Repository\Values\Content\LocationQuery $query
273
     * @param array $languageFilter Configuration for specifying prioritized languages query will be performed on.
274
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
275
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations
276
     * @param bool $filterOnUserPermissions if true only the objects which is the user allowed to read are returned.
277
     *
278
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
279
     */
280
    public function findLocations(LocationQuery $query, array $languageFilter = [], bool $filterOnUserPermissions = true): SearchResult
281
    {
282
        if (!is_int($query->offset)) {
283
            throw new InvalidArgumentType(
284
                '$query->offset',
285
                'integer',
286
                $query->offset
287
            );
288
        }
289
290
        if (!is_int($query->limit)) {
291
            throw new InvalidArgumentType(
292
                '$query->limit',
293
                'integer',
294
                $query->limit
295
            );
296
        }
297
298
        $query = clone $query;
299
        $query->filter = $query->filter ?: new Criterion\MatchAll();
300
301 View Code Duplication
        if ($filterOnUserPermissions && !$this->addPermissionsCriterion($query->filter)) {
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...
302
            return new SearchResult(['time' => 0, 'totalCount' => 0]);
303
        }
304
305
        $result = $this->searchHandler->findLocations($query, $languageFilter);
306
307
        $missingLocations = $this->contentDomainMapper->buildLocationDomainObjectsOnSearchResult($result, $languageFilter);
308
        foreach ($missingLocations as $missingLocation) {
309
            $this->backgroundIndexer->registerLocation($missingLocation);
310
        }
311
312
        return $result;
313
    }
314
315
    /**
316
     * Adds content, read Permission criteria if needed and return false if no access at all.
317
     *
318
     * @uses \eZ\Publish\API\Repository\PermissionCriterionResolver::getPermissionsCriterion()
319
     *
320
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $criterion
321
     *
322
     * @return bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
323
     */
324 View Code Duplication
    protected function addPermissionsCriterion(Criterion &$criterion)
325
    {
326
        $permissionCriterion = $this->permissionCriterionResolver->getPermissionsCriterion('content', 'read');
327
        if ($permissionCriterion === true || $permissionCriterion === false) {
328
            return $permissionCriterion;
329
        }
330
331
        // Merge with original $criterion
332
        if ($criterion instanceof LogicalAnd) {
333
            $criterion->criteria[] = $permissionCriterion;
334
        } else {
335
            $criterion = new LogicalAnd(
336
                [
337
                    $criterion,
338
                    $permissionCriterion,
339
                ]
340
            );
341
        }
342
343
        return true;
344
    }
345
346
    public function supports(int $capabilityFlag): bool
347
    {
348
        if ($this->searchHandler instanceof Capable) {
349
            return $this->searchHandler->supports($capabilityFlag);
350
        }
351
352
        return false;
353
    }
354
}
355