Completed
Push — ezp_31440 ( 87ccef )
by
unknown
18:57 queued 03:24
created

SearchService::validateContentCriteria()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 2
dl 0
loc 14
rs 9.7998
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
    public function find(Query $query, array $languageFilter = [], bool $filterOnUserPermissions = true): SearchResult
0 ignored issues
show
Unused Code introduced by
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $languageFilter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $filterOnUserPermissions is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

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