1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22/**
23 * Class containing methods for operations with histories.
24 */
25class CHistory extends CApiService {
26
27	public const ACCESS_RULES = [
28		'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER]
29	];
30
31	protected $tableName;
32	protected $tableAlias = 'h';
33	protected $sortColumns = ['itemid', 'clock'];
34
35	public function __construct() {
36		// considering the quirky nature of the history API,
37		// the parent::__construct() method should not be called.
38	}
39
40	/**
41	 * Get history data.
42	 *
43	 * @param array  $options
44	 * @param int    $options['history']                       History object type to return.
45	 * @param array  $options['hostids']                       Return only history from the given hosts.
46	 * @param array  $options['itemids']                       Return only history from the given items.
47	 * @param int    $options['time_from']                     Return only values that have been received after or at
48	 *                                                         the given time.
49	 * @param int    $options['time_till']                     Return only values that have been received before or at
50	 *                                                         the given time.
51	 * @param array  $options['filter']                        Return only those results that exactly match the given
52	 *                                                         filter.
53	 * @param int    $options['filter']['itemid']
54	 * @param int    $options['filter']['clock']
55	 * @param mixed  $options['filter']['value']
56	 * @param int    $options['filter']['ns']
57	 * @param array  $options['search']                        Return results that match the given wildcard search
58	 *                                                         (case-insensitive).
59	 * @param string $options['search']['value']
60	 * @param bool   $options['searchByAny']                   If set to true return results that match any of the
61	 *                                                         criteria given in the filter or search parameter instead
62	 *                                                         of all of them.
63	 * @param bool   $options['startSearch']                   Return results that match the given wildcard search
64	 *                                                         (case-insensitive).
65	 * @param bool   $options['excludeSearch']                 Return results that do not match the criteria given in
66	 *                                                         the search parameter.
67	 * @param bool   $options['searchWildcardsEnabled']        If set to true enables the use of "*" as a wildcard
68	 *                                                         character in the search parameter.
69	 * @param array  $options['output']                        Object properties to be returned.
70	 * @param bool   $options['countOutput']                   Return the number of records in the result instead of the
71	 *                                                         actual data.
72	 * @param array  $options['sortfield']                     Sort the result by the given properties. Refer to a
73	 *                                                         specific API get method description for a list of
74	 *                                                         properties that can be used for sorting. Macros are not
75	 *                                                         expanded before sorting.
76	 * @param array  $options['sortorder']                     Order of sorting. If an array is passed, each value will
77	 *                                                         be matched to the corresponding property given in the
78	 *                                                         sortfield parameter.
79	 * @param int    $options['limit']                         Limit the number of records returned.
80	 * @param bool   $options['editable']                      If set to true return only objects that the user has
81	 *                                                         write permissions to.
82	 *
83	 * @throws Exception
84	 * @return array|int    Data array or number of rows.
85	 */
86	public function get($options = []) {
87		$value_types = [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_UINT64,
88			ITEM_VALUE_TYPE_TEXT
89		];
90		$common_value_types = [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_UINT64,
91			ITEM_VALUE_TYPE_TEXT
92		];
93
94		$api_input_rules = ['type' => API_OBJECT, 'fields' => [
95			// filter
96			'history' =>				['type' => API_INT32, 'in' => implode(',', $value_types), 'default' => ITEM_VALUE_TYPE_UINT64],
97			'hostids' =>				['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null],
98			'itemids' =>				['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null],
99			'time_from' =>				['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'default' => null],
100			'time_till' =>				['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'default' => null],
101			'filter' =>					['type' => API_MULTIPLE, 'default' => null, 'rules' => [
102											['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_LOG])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => [
103					'itemid' =>					['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
104					'clock' =>					['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
105					'timestamp' =>				['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
106					'source' =>					['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
107					'severity' =>				['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
108					'logeventid' =>				['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
109					'ns' =>						['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE]
110				]],
111											['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_TEXT])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => [
112					'itemid' =>					['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
113					'clock' =>					['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
114					'ns' =>						['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE]
115				]],
116											['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_UINT64])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => [
117					'itemid' =>					['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
118					'clock' =>					['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
119					'ns' =>						['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
120					'value' =>					['type' => API_UINTS64, 'flags' => API_ALLOW_NULL | API_NORMALIZE]
121				]],
122											['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => [
123					'itemid' =>					['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
124					'clock' =>					['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
125					'ns' =>						['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
126					'value' =>					['type' => API_FLOATS, 'flags' => API_ALLOW_NULL | API_NORMALIZE]
127				]]
128			]],
129			'search' =>					['type' => API_MULTIPLE, 'default' => null, 'rules' => [
130											['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_LOG])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => [
131					'source' =>					['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
132					'value' =>					['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE]
133				]],
134											['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_TEXT])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => [
135					'value' =>					['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE]
136				]],
137											['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => []]
138			]],
139			'searchByAny' =>			['type' => API_BOOLEAN, 'default' => false],
140			'startSearch' =>			['type' => API_FLAG, 'default' => false],
141			'excludeSearch' =>			['type' => API_FLAG, 'default' => false],
142			'searchWildcardsEnabled' =>	['type' => API_BOOLEAN, 'default' => false],
143			// output
144			'output' =>					['type' => API_MULTIPLE, 'default' => API_OUTPUT_EXTEND, 'rules' => [
145											['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_LOG])], 'type' => API_OUTPUT, 'in' => implode(',', ['itemid', 'clock', 'timestamp', 'source', 'severity', 'value', 'logeventid', 'ns'])],
146											['if' => ['field' => 'history', 'in' => implode(',', $common_value_types)], 'type' => API_OUTPUT, 'in' => implode(',', ['itemid', 'clock', 'value', 'ns'])]
147			]],
148			'countOutput' =>			['type' => API_FLAG, 'default' => false],
149			// sort and limit
150			'sortfield' =>				['type' => API_STRINGS_UTF8, 'flags' => API_NORMALIZE, 'in' => implode(',', $this->sortColumns), 'uniq' => true, 'default' => []],
151			'sortorder' =>				['type' => API_SORTORDER, 'default' => []],
152			'limit' =>					['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '1:'.ZBX_MAX_INT32, 'default' => null],
153			// flags
154			'editable' =>				['type' => API_BOOLEAN, 'default' => false]
155		]];
156
157		if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) {
158			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
159		}
160
161		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN || $options['hostids'] !== null) {
162			$items = API::Item()->get([
163				'output' => ['itemid'],
164				'itemids' => $options['itemids'],
165				'hostids' => $options['hostids'],
166				'editable' => $options['editable'],
167				'webitems' => true,
168				'preservekeys' => true
169			]);
170			$options['itemids'] = array_keys($items);
171		}
172
173		$this->tableName = CHistoryManager::getTableName($options['history']);
174
175		switch (CHistoryManager::getDataSourceType($options['history'])) {
176			case ZBX_HISTORY_SOURCE_ELASTIC:
177				return $this->getFromElasticsearch($options);
178
179			default:
180				return $this->getFromSql($options);
181		}
182	}
183
184	/**
185	 * SQL specific implementation of get.
186	 *
187	 * @see CHistory::get
188	 */
189	private function getFromSql($options) {
190		$result = [];
191		$sql_parts = [
192			'select'	=> ['history' => 'h.itemid'],
193			'from'		=> [
194				'history' => $this->tableName.' h'
195			],
196			'where'		=> [],
197			'group'		=> [],
198			'order'		=> []
199		];
200
201		// itemids
202		if ($options['itemids'] !== null) {
203			$sql_parts['where']['itemid'] = dbConditionInt('h.itemid', $options['itemids']);
204		}
205
206		// time_from
207		if ($options['time_from'] !== null) {
208			$sql_parts['where']['clock_from'] = 'h.clock>='.$options['time_from'];
209		}
210
211		// time_till
212		if ($options['time_till'] !== null) {
213			$sql_parts['where']['clock_till'] = 'h.clock<='.$options['time_till'];
214		}
215
216		// filter
217		if ($options['filter'] !== null) {
218			$this->dbFilter($sql_parts['from']['history'], $options, $sql_parts);
219		}
220
221		// search
222		if ($options['search'] !== null) {
223			zbx_db_search($sql_parts['from']['history'], $options, $sql_parts);
224		}
225
226		$sql_parts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sql_parts);
227		$sql_parts = $this->applyQuerySortOptions($this->tableName, $this->tableAlias(), $options, $sql_parts);
228
229		$db_res = DBselect(self::createSelectQueryFromParts($sql_parts), $options['limit']);
230
231		while ($data = DBfetch($db_res)) {
232			if ($options['countOutput']) {
233				$result = $data['rowscount'];
234			}
235			else {
236				$result[] = $data;
237			}
238		}
239
240		return $result;
241	}
242
243	/**
244	 * Elasticsearch specific implementation of get.
245	 *
246	 * @see CHistory::get
247	 */
248	private function getFromElasticsearch($options) {
249		$query = [];
250		$schema = DB::getSchema($this->tableName);
251
252		// itemids
253		if ($options['itemids'] !== null) {
254			$query['query']['bool']['must'][] = [
255				'terms' => [
256					'itemid' => $options['itemids']
257				]
258			];
259		}
260
261		// time_from
262		if ($options['time_from'] !== null) {
263			$query['query']['bool']['must'][] = [
264				'range' => [
265					'clock' => [
266						'gte' => $options['time_from']
267					]
268				]
269			];
270		}
271
272		// time_till
273		if ($options['time_till'] !== null) {
274			$query['query']['bool']['must'][] = [
275				'range' => [
276					'clock' => [
277						'lte' => $options['time_till']
278					]
279				]
280			];
281		}
282
283		// filter
284		if ($options['filter'] !== null) {
285			$query = CElasticsearchHelper::addFilter(DB::getSchema($this->tableName), $query, $options);
286		}
287
288		// search
289		if ($options['search'] !== null) {
290			$query = CElasticsearchHelper::addSearch($schema, $query, $options);
291		}
292
293		// output
294		if ($options['countOutput'] === false && $options['output'] !== API_OUTPUT_EXTEND) {
295			$query['_source'] = $options['output'];
296		}
297
298		// sorting
299		if ($options['sortfield']) {
300			$query = CElasticsearchHelper::addSort($query, $options);
301		}
302
303		// limit
304		if ($options['limit'] !== null) {
305			$query['size'] = $options['limit'];
306		}
307
308		$endpoints = CHistoryManager::getElasticsearchEndpoints($options['history']);
309		if ($endpoints) {
310			return CElasticsearchHelper::query('POST', reset($endpoints), $query);
311		}
312
313		return null;
314	}
315}
316