1<?php
2
3declare(strict_types = 1);
4
5namespace Elasticsearch\Endpoints;
6
7use Elasticsearch\Common\Exceptions\UnexpectedValueException;
8use Elasticsearch\Serializers\SerializerInterface;
9use Elasticsearch\Transport;
10use Exception;
11use GuzzleHttp\Ring\Future\FutureArrayInterface;
12
13/**
14 * Class AbstractEndpoint
15 *
16 * @category Elasticsearch
17 * @package  Elasticsearch\Endpoints
18 * @author   Zachary Tong <zach@elastic.co>
19 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
20 * @link     http://elastic.co
21 */
22abstract class AbstractEndpoint
23{
24    /**
25     * @var array
26     */
27    protected $params =[];
28
29    /**
30     * @var string
31     */
32    protected $index = null;
33
34    /**
35     * @var string
36     */
37    protected $type = null;
38
39    /**
40     * @var string|int
41     */
42    protected $id = null;
43
44    /**
45     * @var string
46     */
47    protected $method = null;
48
49    /**
50     * @var string|array
51     */
52    protected $body = null;
53
54    /**
55     * @var array
56     */
57    private $options = [];
58
59    /**
60     * @var SerializerInterface
61     */
62    protected $serializer;
63
64    /**
65     * @return string[]
66     */
67    abstract public function getParamWhitelist(): array;
68
69    /**
70     * @return string
71     */
72    abstract public function getURI(): string;
73
74    /**
75     * @return string
76     */
77    abstract public function getMethod(): string;
78
79
80    /**
81     * Set the parameters for this endpoint
82     *
83     * @param  string[] $params Array of parameters
84     * @return $this
85     */
86    public function setParams(array $params)
87    {
88        $this->extractOptions($params);
89        $this->checkUserParams($params);
90        $params = $this->convertCustom($params);
91        $this->params = $this->convertArraysToStrings($params);
92
93        return $this;
94    }
95
96    public function getParams(): array
97    {
98        return $this->params;
99    }
100
101    public function getOptions(): array
102    {
103        return $this->options;
104    }
105
106    public function getIndex(): ?string
107    {
108        return $this->index;
109    }
110
111    /**
112     * @return $this
113     */
114    public function setIndex($index)
115    {
116        if ($index === null) {
117            return $this;
118        }
119
120        if (is_array($index) === true) {
121            $index = array_map('trim', $index);
122            $index = implode(",", $index);
123        }
124
125        $this->index = urlencode($index);
126
127        return $this;
128    }
129
130    /**
131     * @deprecated
132     */
133    public function getType(): ?string
134    {
135        return $this->type;
136    }
137
138    /**
139     * @deprecated
140     * @return     $this
141     */
142    public function setType(?string $type)
143    {
144        if ($type === null) {
145            return $this;
146        }
147
148        if (is_array($type) === true) {
149            $type = array_map('trim', $type);
150            $type = implode(",", $type);
151        }
152
153        $this->type = urlencode($type);
154
155        return $this;
156    }
157
158    /**
159     * @param int|string $docID
160     *
161     * @return $this
162     */
163    public function setID($docID)
164    {
165        if ($docID === null) {
166            return $this;
167        }
168
169        if (is_int($docID)) {
170            $docID = (string) $docID;
171        }
172
173        $this->id = urlencode($docID);
174
175        return $this;
176    }
177
178    /**
179     * @return array|string
180     */
181    public function getBody()
182    {
183        return $this->body;
184    }
185
186    protected function getOptionalURI(string $endpoint): string
187    {
188        $uri = [];
189        $uri[] = $this->getOptionalIndex();
190        $uri[] = $this->getOptionalType();
191        $uri[] = $endpoint;
192        $uri =  array_filter($uri);
193
194        return '/' . implode('/', $uri);
195    }
196
197    private function getOptionalIndex(): string
198    {
199        if (isset($this->index) === true) {
200            return $this->index;
201        } else {
202            return '_all';
203        }
204    }
205
206    private function getOptionalType(): string
207    {
208        if (isset($this->type) === true) {
209            return $this->type;
210        } else {
211            return '';
212        }
213    }
214
215    /**
216     * @param array $params
217     *
218     * @throws UnexpectedValueException
219     */
220    private function checkUserParams(array $params)
221    {
222        if (isset($params) !== true) {
223            return; //no params, just return.
224        }
225
226        $whitelist = array_merge(
227            $this->getParamWhitelist(),
228            [ 'pretty', 'human', 'error_trace', 'source', 'filter_path' ]
229        );
230
231        $invalid = array_diff(array_keys($params), $whitelist);
232        if (count($invalid) > 0) {
233            sort($invalid);
234            sort($whitelist);
235            throw new UnexpectedValueException(
236                sprintf(
237                    (count($invalid) > 1 ? '"%s" are not valid parameters.' : '"%s" is not a valid parameter.').' Allowed parameters are "%s"',
238                    implode('", "', $invalid),
239                    implode('", "', $whitelist)
240                )
241            );
242        }
243    }
244
245    /**
246     * @param array $params Note: this is passed by-reference!
247     */
248    private function extractOptions(&$params)
249    {
250        // Extract out client options, then start transforming
251        if (isset($params['client']) === true) {
252            $this->options['client'] = $params['client'];
253            unset($params['client']);
254        }
255        $ignore = isset($this->options['client']['ignore']) ? $this->options['client']['ignore'] : null;
256        if (isset($ignore) === true) {
257            if (is_string($ignore)) {
258                $this->options['client']['ignore'] = explode(",", $ignore);
259            } elseif (is_array($ignore)) {
260                $this->options['client']['ignore'] = $ignore;
261            } else {
262                $this->options['client']['ignore'] = [$ignore];
263            }
264        }
265    }
266
267    private function convertCustom(array $params): array
268    {
269        if (isset($params['custom']) === true) {
270            foreach ($params['custom'] as $k => $v) {
271                $params[$k] = $v;
272            }
273            unset($params['custom']);
274        }
275
276        return $params;
277    }
278
279    private function convertArraysToStrings(array $params): array
280    {
281        foreach ($params as $key => &$value) {
282            if (!($key === 'client' || $key == 'custom') && is_array($value) === true) {
283                if ($this->isNestedArray($value) !== true) {
284                    $value = implode(",", $value);
285                }
286            }
287        }
288
289        return $params;
290    }
291
292    private function isNestedArray(array $a): bool
293    {
294        foreach ($a as $v) {
295            if (is_array($v)) {
296                return true;
297            }
298        }
299
300        return false;
301    }
302}
303