Completed
Push — master ( ad5129...40a52b )
by André
57:57 queued 44:53
created

SearchService::findSingle()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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