1<?php
2
3namespace Elastica;
4
5use Elastica\Aggregation\AbstractAggregation;
6use Elastica\Exception\InvalidException;
7use Elastica\Query\AbstractQuery;
8use Elastica\Query\MatchAll;
9use Elastica\Query\QueryString;
10use Elastica\Script\AbstractScript;
11use Elastica\Script\ScriptFields;
12use Elastica\Suggest\AbstractSuggest;
13
14/**
15 * Elastica query object.
16 *
17 * Creates different types of queries
18 *
19 * @author Nicolas Ruflin <spam@ruflin.com>
20 *
21 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html
22 */
23class Query extends Param
24{
25    /**
26     * Suggest query or not.
27     *
28     * @var int Suggest
29     */
30    protected $_suggest = 0;
31
32    /**
33     * Creates a query object.
34     *
35     * @param array|\Elastica\Query\AbstractQuery $query OPTIONAL Query object (default = null)
36     */
37    public function __construct($query = null)
38    {
39        if (\is_array($query)) {
40            $this->setRawQuery($query);
41        } elseif ($query instanceof AbstractQuery) {
42            $this->setQuery($query);
43        } elseif ($query instanceof Suggest) {
44            $this->setSuggest($query);
45        }
46    }
47
48    /**
49     * Transforms the argument to a query object.
50     *
51     * For example, an empty argument will return a \Elastica\Query with a \Elastica\Query\MatchAll.
52     *
53     * @param mixed $query
54     *
55     * @throws InvalidException For an invalid argument
56     *
57     * @return self
58     */
59    public static function create($query)
60    {
61        switch (true) {
62            case $query instanceof self:
63                return $query;
64            case $query instanceof AbstractQuery:
65                return new self($query);
66            case empty($query):
67                return new self(new MatchAll());
68            case \is_array($query):
69                return new self($query);
70            case \is_string($query):
71                return new self(new QueryString($query));
72            case $query instanceof AbstractSuggest:
73                return new self(new Suggest($query));
74
75            case $query instanceof Suggest:
76                return new self($query);
77        }
78
79        throw new InvalidException('Unexpected argument to create a query for.');
80    }
81
82    /**
83     * Sets query as raw array. Will overwrite all already set arguments.
84     *
85     * @param array $query Query array
86     *
87     * @return $this
88     */
89    public function setRawQuery(array $query)
90    {
91        $this->_params = $query;
92
93        return $this;
94    }
95
96    /**
97     * Sets the query.
98     *
99     * @param \Elastica\Query\AbstractQuery $query Query object
100     *
101     * @return $this
102     */
103    public function setQuery(AbstractQuery $query)
104    {
105        return $this->setParam('query', $query);
106    }
107
108    /**
109     * Gets the query object.
110     *
111     * @return \Elastica\Query\AbstractQuery
112     **/
113    public function getQuery()
114    {
115        return $this->getParam('query');
116    }
117
118    /**
119     * Sets the start from which the search results should be returned.
120     *
121     * @param int $from
122     *
123     * @return $this
124     */
125    public function setFrom($from)
126    {
127        return $this->setParam('from', $from);
128    }
129
130    /**
131     * Sets sort arguments for the query
132     * Replaces existing values.
133     *
134     * @param array $sortArgs Sorting arguments
135     *
136     * @return $this
137     *
138     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html
139     */
140    public function setSort(array $sortArgs)
141    {
142        return $this->setParam('sort', $sortArgs);
143    }
144
145    /**
146     * Adds a sort param to the query.
147     *
148     * @param mixed $sort Sort parameter
149     *
150     * @return $this
151     *
152     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html
153     */
154    public function addSort($sort)
155    {
156        return $this->addParam('sort', $sort);
157    }
158
159    /**
160     * Keep track of the scores when sorting results.
161     *
162     * @param bool $trackScores
163     *
164     * @return $this
165     *
166     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html#_track_scores
167     */
168    public function setTrackScores($trackScores = true)
169    {
170        return $this->setParam('track_scores', (bool) $trackScores);
171    }
172
173    /**
174     * Sets highlight arguments for the query.
175     *
176     * @param array $highlightArgs Set all highlight arguments
177     *
178     * @return $this
179     *
180     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-highlighting.html
181     */
182    public function setHighlight(array $highlightArgs)
183    {
184        return $this->setParam('highlight', $highlightArgs);
185    }
186
187    /**
188     * Adds a highlight argument.
189     *
190     * @param mixed $highlight Add highlight argument
191     *
192     * @return $this
193     *
194     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-highlighting.html
195     */
196    public function addHighlight($highlight)
197    {
198        return $this->addParam('highlight', $highlight);
199    }
200
201    /**
202     * Sets maximum number of results for this query.
203     *
204     * @param int $size OPTIONAL Maximal number of results for query (default = 10)
205     *
206     * @return $this
207     */
208    public function setSize($size = 10)
209    {
210        return $this->setParam('size', $size);
211    }
212
213    /**
214     * Enables explain on the query.
215     *
216     * @param bool $explain OPTIONAL Enabled or disable explain (default = true)
217     *
218     * @return $this
219     *
220     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-explain.html
221     */
222    public function setExplain($explain = true)
223    {
224        return $this->setParam('explain', $explain);
225    }
226
227    /**
228     * Enables version on the query.
229     *
230     * @param bool $version OPTIONAL Enabled or disable version (default = true)
231     *
232     * @return $this
233     *
234     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-version.html
235     */
236    public function setVersion($version = true)
237    {
238        return $this->setParam('version', $version);
239    }
240
241    /**
242     * Sets the fields to be returned by the search
243     * NOTICE php will encode modified(or named keys) array into object format in json format request
244     * so the fields array must a sequence(list) type of array.
245     *
246     * @param array $fields Fields to be returned
247     *
248     * @return $this
249     *
250     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-fields.html
251     */
252    public function setStoredFields(array $fields)
253    {
254        return $this->setParam('stored_fields', $fields);
255    }
256
257    /**
258     * Sets the fields not stored to be returned by the search.
259     *
260     * @param array $fieldDataFields Fields not stored to be returned
261     *
262     * @return $this
263     *
264     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-fielddata-fields.html
265     */
266    public function setFieldDataFields(array $fieldDataFields)
267    {
268        return $this->setParam('docvalue_fields', $fieldDataFields);
269    }
270
271    /**
272     * Set script fields.
273     *
274     * @param array|\Elastica\Script\ScriptFields $scriptFields Script fields
275     *
276     * @return $this
277     *
278     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-script-fields.html
279     */
280    public function setScriptFields($scriptFields)
281    {
282        if (\is_array($scriptFields)) {
283            $scriptFields = new ScriptFields($scriptFields);
284        }
285
286        return $this->setParam('script_fields', $scriptFields);
287    }
288
289    /**
290     * Adds a Script to the query.
291     *
292     * @param string                          $name
293     * @param \Elastica\Script\AbstractScript $script Script object
294     *
295     * @return $this
296     */
297    public function addScriptField($name, AbstractScript $script)
298    {
299        if (isset($this->_params['script_fields'])) {
300            $this->_params['script_fields']->addScript($name, $script);
301        } else {
302            $this->setScriptFields([$name => $script]);
303        }
304
305        return $this;
306    }
307
308    /**
309     * Adds an Aggregation to the query.
310     *
311     * @param AbstractAggregation $agg
312     *
313     * @return $this
314     */
315    public function addAggregation(AbstractAggregation $agg)
316    {
317        $this->_params['aggs'][] = $agg;
318
319        return $this;
320    }
321
322    /**
323     * Converts all query params to an array.
324     *
325     * @return array Query array
326     */
327    public function toArray()
328    {
329        if (!isset($this->_params['query']) && (0 == $this->_suggest)) {
330            $this->setQuery(new MatchAll());
331        }
332
333        if (isset($this->_params['post_filter']) && 0 === \count(($this->_params['post_filter'])->toArray())) {
334            unset($this->_params['post_filter']);
335        }
336
337        $array = $this->_convertArrayable($this->_params);
338
339        if (isset($array['suggest'])) {
340            $array['suggest'] = $array['suggest']['suggest'];
341        }
342
343        return $array;
344    }
345
346    /**
347     * Allows filtering of documents based on a minimum score.
348     *
349     * @param float $minScore Minimum score to filter documents by
350     *
351     * @throws \Elastica\Exception\InvalidException
352     *
353     * @return $this
354     */
355    public function setMinScore($minScore)
356    {
357        if (!\is_numeric($minScore)) {
358            throw new InvalidException('has to be numeric param');
359        }
360
361        return $this->setParam('min_score', $minScore);
362    }
363
364    /**
365     * Add a suggest term.
366     *
367     * @param \Elastica\Suggest $suggest suggestion object
368     *
369     * @return $this
370     */
371    public function setSuggest(Suggest $suggest)
372    {
373        $this->setParam('suggest', $suggest);
374
375        $this->_suggest = 1;
376
377        return $this;
378    }
379
380    /**
381     * Add a Rescore.
382     *
383     * @param mixed $rescore suggestion object
384     *
385     * @return $this
386     */
387    public function setRescore($rescore)
388    {
389        if (\is_array($rescore)) {
390            $buffer = [];
391
392            foreach ($rescore as $rescoreQuery) {
393                $buffer[] = $rescoreQuery;
394            }
395        } else {
396            $buffer = $rescore;
397        }
398
399        return $this->setParam('rescore', $buffer);
400    }
401
402    /**
403     * Sets the _source field to be returned with every hit.
404     *
405     * @param array|bool $params Fields to be returned or false to disable source
406     *
407     * @return $this
408     *
409     * @see   https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-source-filtering.html
410     */
411    public function setSource($params)
412    {
413        return $this->setParam('_source', $params);
414    }
415
416    /**
417     * Sets post_filter argument for the query. The filter is applied after the query has executed.
418     *
419     * @param array|\Elastica\Query\AbstractQuery $filter
420     *
421     * @return $this
422     *
423     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-post-filter.html
424     */
425    public function setPostFilter(AbstractQuery $filter)
426    {
427        return $this->setParam('post_filter', $filter);
428    }
429}
430