Completed
Push — tolerant_search_service ( 516a96 )
by André
17:39
created

SearchService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 5
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the eZ\Publish\Core\Repository\SearchService class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Repository;
10
11
use eZ\Publish\API\Repository\SearchService as SearchServiceInterface;
12
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
13
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalOperator;
14
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Location as LocationCriterion;
15
use eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location as LocationSortClause;
16
use eZ\Publish\API\Repository\Values\Content\Query;
17
use eZ\Publish\API\Repository\Values\Content\LocationQuery;
18
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
19
use eZ\Publish\API\Repository\Values\Content\Search\SearchResult;
20
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
21
use eZ\Publish\API\Repository\Exceptions\UnauthorizedException as APIUnauthorizedException;
22
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
23
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
24
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType;
25
use eZ\Publish\SPI\Search\Handler;
26
27
/**
28
 * Search service.
29
 */
30
class SearchService implements SearchServiceInterface
31
{
32
    /**
33
     * @var \eZ\Publish\Core\Repository\Repository
34
     */
35
    protected $repository;
36
37
    /**
38
     * @var \eZ\Publish\SPI\Search\Handler
39
     */
40
    protected $searchHandler;
41
42
    /**
43
     * @var array
44
     */
45
    protected $settings;
46
47
    /**
48
     * @var \eZ\Publish\Core\Repository\Helper\DomainMapper
49
     */
50
    protected $domainMapper;
51
52
    /**
53
     * @var \eZ\Publish\Core\Repository\PermissionsCriterionHandler
54
     */
55
    protected $permissionsCriterionHandler;
56
57
    /**
58
     * Setups service with reference to repository object that created it & corresponding handler.
59
     *
60
     * @param \eZ\Publish\API\Repository\Repository $repository
61
     * @param \eZ\Publish\SPI\Search\Handler $searchHandler
62
     * @param \eZ\Publish\Core\Repository\Helper\DomainMapper $domainMapper
63
     * @param \eZ\Publish\Core\Repository\PermissionsCriterionHandler $permissionsCriterionHandler
64
     * @param array $settings
65
     */
66
    public function __construct(
67
        RepositoryInterface $repository,
68
        Handler $searchHandler,
69
        Helper\DomainMapper $domainMapper,
70
        PermissionsCriterionHandler $permissionsCriterionHandler,
71
        array $settings = array()
72
    ) {
73
        $this->repository = $repository;
0 ignored issues
show
Documentation Bug introduced by
$repository is of type object<eZ\Publish\API\Repository\Repository>, but the property $repository was declared to be of type object<eZ\Publish\Core\Repository\Repository>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
74
        $this->searchHandler = $searchHandler;
75
        $this->domainMapper = $domainMapper;
76
        // Union makes sure default settings are ignored if provided in argument
77
        $this->settings = $settings + array(
78
            //'defaultSetting' => array(),
79
        );
80
        $this->permissionsCriterionHandler = $permissionsCriterionHandler;
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 = array(), $filterOnUserPermissions = true)
97
    {
98
        $contentService = $this->repository->getContentService();
99
        $result = $this->internalFindContentInfo($query, $languageFilter, $filterOnUserPermissions);
100
        foreach ($result->searchHits as $key => $hit) {
101
            try {
102
                // As we get ContentInfo from SPI, we need to load full content (avoids getting stale content data)
103
                $hit->valueObject = $contentService->internalLoadContent(
0 ignored issues
show
Bug introduced by
The method internalLoadContent() does not exist on eZ\Publish\API\Repository\ContentService. Did you maybe mean loadContent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
104
                    $hit->valueObject->id,
105
                    (!empty($languageFilter['languages']) ? $languageFilter['languages'] : null),
106
                    null,
107
                    false,
108
                    (isset($languageFilter['useAlwaysAvailable']) ? $languageFilter['useAlwaysAvailable'] : true)
109
                );
110
            } catch (APINotFoundException $e) {
111
                // Most likely stale data. @todo Pass info about this to background indexer
112
                unset($result->searchHits[$key]);
113
            } catch (APIUnauthorizedException $e) {
114
                // Most likely stale cached permission criterion, just skip
115
                unset($result->searchHits[$key]);
116
            }
117
        }
118
119
        return $result;
120
    }
121
122
    /**
123
     * Finds contentInfo objects for the given query.
124
     *
125
     * @see SearchServiceInterface::findContentInfo()
126
     *
127
     * @since 5.4.5
128
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if query is not valid
129
     *
130
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
131
     * @param array $languageFilter - a map of filters for the returned fields.
132
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
133
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations.
134
     * @param bool $filterOnUserPermissions if true (default) only the objects which is the user allowed to read are returned.
135
     *
136
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
137
     */
138
    public function findContentInfo(Query $query, array $languageFilter = array(), $filterOnUserPermissions = true)
139
    {
140
        $result = $this->internalFindContentInfo($query, $languageFilter, $filterOnUserPermissions);
141
        foreach ($result->searchHits as $hit) {
142
            $hit->valueObject = $this->domainMapper->buildContentInfoDomainObject(
143
                $hit->valueObject
144
            );
145
        }
146
147
        return $result;
148
    }
149
150
    /**
151
     * Finds SPI content info objects for the given query.
152
     *
153
     * Internal for use by {@link findContent} and {@link findContentInfo}.
154
     *
155
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if query is not valid
156
     *
157
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
158
     * @param array $languageFilter - a map of filters for the returned fields.
159
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
160
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations.
161
     * @param bool $filterOnUserPermissions if true only the objects which is the user allowed to read are returned.
162
     *
163
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult With "raw" SPI contentInfo objects in result
164
     */
165
    protected function internalFindContentInfo(Query $query, array $languageFilter = array(), $filterOnUserPermissions = true)
166
    {
167
        if (!is_int($query->offset)) {
168
            throw new InvalidArgumentType(
169
                '$query->offset',
170
                'integer',
171
                $query->offset
172
            );
173
        }
174
175
        if (!is_int($query->limit)) {
176
            throw new InvalidArgumentType(
177
                '$query->limit',
178
                'integer',
179
                $query->limit
180
            );
181
        }
182
183
        $query = clone $query;
184
        $query->filter = $query->filter ?: new Criterion\MatchAll();
185
186
        $this->validateContentCriteria(array($query->query), '$query');
187
        $this->validateContentCriteria(array($query->filter), '$query');
188
        $this->validateContentSortClauses($query);
189
190 View Code Duplication
        if ($filterOnUserPermissions && !$this->permissionsCriterionHandler->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...
191
            return new SearchResult(array('time' => 0, 'totalCount' => 0));
192
        }
193
194
        return $this->searchHandler->findContent($query, $languageFilter);
195
    }
196
197
    /**
198
     * Checks that $criteria does not contain Location criterions.
199
     *
200
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
201
     *
202
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion[] $criteria
203
     * @param string $argumentName
204
     */
205
    protected function validateContentCriteria(array $criteria, $argumentName)
206
    {
207
        foreach ($criteria as $criterion) {
208
            if ($criterion instanceof LocationCriterion) {
209
                throw new InvalidArgumentException(
210
                    $argumentName,
211
                    'Location criterions cannot be used in Content search'
212
                );
213
            }
214
            if ($criterion instanceof LogicalOperator) {
215
                $this->validateContentCriteria($criterion->criteria, $argumentName);
216
            }
217
        }
218
    }
219
220
    /**
221
     * Checks that $query does not contain Location sort clauses.
222
     *
223
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
224
     *
225
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
226
     */
227
    protected function validateContentSortClauses(Query $query)
228
    {
229
        foreach ($query->sortClauses as $sortClause) {
230
            if ($sortClause instanceof LocationSortClause) {
231
                throw new InvalidArgumentException('$query', 'Location sort clauses cannot be used in Content search');
232
            }
233
        }
234
    }
235
236
    /**
237
     * Performs a query for a single content object.
238
     *
239
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the object was not found by the query or due to permissions
240
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if criterion is not valid
241
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is more than one result matching the criterions
242
     *
243
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
244
     * @param array $languageFilter Configuration for specifying prioritized languages query will be performed on.
245
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
246
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations.
247
     * @param bool $filterOnUserPermissions if true only the objects which is the user allowed to read are returned.
248
     *
249
     * @return \eZ\Publish\API\Repository\Values\Content\Content
250
     */
251
    public function findSingle(Criterion $filter, array $languageFilter = array(), $filterOnUserPermissions = true)
252
    {
253
        $this->validateContentCriteria(array($filter), '$filter');
254
255
        if ($filterOnUserPermissions && !$this->permissionsCriterionHandler->addPermissionsCriterion($filter)) {
256
            throw new NotFoundException('Content', '*');
257
        }
258
259
        $contentInfo = $this->searchHandler->findSingle($filter, $languageFilter);
260
261
        return $this->repository->getContentService()->internalLoadContent(
0 ignored issues
show
Bug introduced by
The method internalLoadContent() does not exist on eZ\Publish\API\Repository\ContentService. Did you maybe mean loadContent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
262
            $contentInfo->id,
263
            (!empty($languageFilter['languages']) ? $languageFilter['languages'] : null),
264
            null,
265
            false,
266
            (isset($languageFilter['useAlwaysAvailable']) ? $languageFilter['useAlwaysAvailable'] : true)
267
        );
268
    }
269
270
    /**
271
     * Suggests a list of values for the given prefix.
272
     *
273
     * @param string $prefix
274
     * @param string[] $fieldPaths
275
     * @param int $limit
276
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
277
     */
278
    public function suggest($prefix, $fieldPaths = array(), $limit = 10, Criterion $filter = null)
279
    {
280
    }
281
282
    /**
283
     * Finds Locations for the given query.
284
     *
285
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if query is not valid
286
     *
287
     * @param \eZ\Publish\API\Repository\Values\Content\LocationQuery $query
288
     * @param array $languageFilter Configuration for specifying prioritized languages query will be performed on.
289
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
290
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations
291
     * @param bool $filterOnUserPermissions if true only the objects which is the user allowed to read are returned.
292
     *
293
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
294
     */
295
    public function findLocations(LocationQuery $query, array $languageFilter = array(), $filterOnUserPermissions = true)
296
    {
297
        if (!is_int($query->offset)) {
298
            throw new InvalidArgumentType(
299
                '$query->offset',
300
                'integer',
301
                $query->offset
302
            );
303
        }
304
305
        if (!is_int($query->limit)) {
306
            throw new InvalidArgumentType(
307
                '$query->limit',
308
                'integer',
309
                $query->limit
310
            );
311
        }
312
313
        $query = clone $query;
314
        $query->filter = $query->filter ?: new Criterion\MatchAll();
315
316 View Code Duplication
        if ($filterOnUserPermissions && !$this->permissionsCriterionHandler->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...
317
            return new SearchResult(array('time' => 0, 'totalCount' => 0));
318
        }
319
320
        $result = $this->searchHandler->findLocations($query, $languageFilter);
321
322
        foreach ($result->searchHits as $key => $hit) {
323
            try {
324
                $hit->valueObject = $this->domainMapper->buildLocationDomainObject(
325
                    $hit->valueObject
326
                );
327
            } catch (APINotFoundException $e) {
328
                // Most likely stale data. @todo Pass info about this to background indexer
329
                unset($result->searchHits[$key]);
330
            } catch (APIUnauthorizedException $e) {
331
                // Most likely stale cached permission criterion, just skip
332
                unset($result->searchHits[$key]);
333
            }
334
        }
335
336
        return $result;
337
    }
338
}
339