Completed
Push — ezp28048_trash_move_when_cant_... ( f2b086...c48dd6 )
by
unknown
26:27 queued 11:24
created

SearchService::suggest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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