Completed
Push — location_content_property ( b180d5 )
by André
16:28
created

SearchService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 13

Duplication

Lines 18
Ratio 100 %

Importance

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