Completed
Push — master ( 2d5a91...5e6a60 )
by André
72:09 queued 53:20
created

SearchService::findContentInfo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
346
                    $criterion,
347
                    $permissionCriterion,
348
                )
349
            );
350
        }
351
352
        return true;
353
    }
354
355
    public function supports($capabilityFlag)
356
    {
357
        if ($this->searchHandler instanceof Capable) {
358
            return $this->searchHandler->supports($capabilityFlag);
359
        }
360
361
        return false;
362
    }
363
}
364