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 hosts.
24 */
25class CHost extends CHostGeneral {
26
27	protected $sortColumns = ['hostid', 'host', 'name', 'status'];
28
29	/**
30	 * Get host data.
31	 *
32	 * @param array         $options
33	 * @param array         $options['groupids']                           Select hosts by group IDs.
34	 * @param array         $options['hostids']                            Select hosts by host IDs.
35	 * @param array         $options['templateids']                        Select hosts by template IDs.
36	 * @param array         $options['interfaceids']                       Select hosts by interface IDs.
37	 * @param array         $options['itemids']                            Select hosts by item IDs.
38	 * @param array         $options['triggerids']                         Select hosts by trigger IDs.
39	 * @param array         $options['maintenanceids']                     Select hosts by maintenance IDs.
40	 * @param array         $options['graphids']                           Select hosts by graph IDs.
41	 * @param array         $options['dserviceids']                        Select hosts by discovery service IDs.
42	 * @param array         $options['httptestids']                        Select hosts by web scenario IDs.
43	 * @param bool          $options['monitored_hosts']                    Return only monitored hosts.
44	 * @param bool          $options['templated_hosts']                    Include templates in result.
45	 * @param bool          $options['proxy_hosts']                        Include proxies in result.
46	 * @param bool          $options['with_items']                         Select hosts only with items.
47	 * @param bool          $options['with_item_prototypes']               Select hosts only with item prototypes.
48	 * @param bool          $options['with_simple_graph_items']            Select hosts only with items suitable for graphs.
49	 * @param bool          $options['with_simple_graph_item_prototypes']  Select hosts only with item prototypes suitable for graphs.
50	 * @param bool          $options['with_monitored_items']               Select hosts only with monitored items.
51	 * @param bool          $options['with_triggers']                      Select hosts only with triggers.
52	 * @param bool          $options['with_monitored_triggers']            Select hosts only with monitored triggers.
53	 * @param bool          $options['with_httptests']                     Select hosts only with http tests.
54	 * @param bool          $options['with_monitored_httptests']           Select hosts only with monitored http tests.
55	 * @param bool          $options['with_graphs']                        Select hosts only with graphs.
56	 * @param bool          $options['with_graph_prototypes']              Select hosts only with graph prototypes.
57	 * @param bool          $options['withProblemsSuppressed']             Select hosts that have suppressed problems. (null - all, true - only suppressed, false - unsuppressed)
58	 * @param bool          $options['editable']                           Select hosts only with read-write permission. Ignored for Super admins.
59	 * @param bool          $options['nopermissions']                      Select hosts by ignoring all permissions. Only available inside API calls.
60	 * @param bool          $options['evaltype']                           Operator for tag filter 0 - AND/OR; 2 - OR.
61	 * @param bool          $options['tags']                               Select hosts by given tags.
62	 * @param bool          $options['severities']                         Select hosts that have only problems with given severities.
63	 * @param bool          $options['inheritedTags']                      Select hosts that have given tags also in their linked templates.
64	 * @param string|array  $options['selectGroups']                       Return a "groups" property with host groups data that the host belongs to.
65	 * @param string|array  $options['selectParentTemplates']              Return a "parentTemplates" property with templates that the host is linked to.
66	 * @param string|array  $options['selectItems']                        Return an "items" property with host items.
67	 * @param string|array  $options['selectDiscoveries']                  Return a "discoveries" property with host low-level discovery rules.
68	 * @param string|array  $options['selectTriggers']                     Return a "triggers" property with host triggers.
69	 * @param string|array  $options['selectGraphs']                       Return a "graphs" property with host graphs.
70	 * @param string|array  $options['selectMacros']                       Return a "macros" property with host macros.
71	 * @param string|array  $options['selectDashboards']                   Return a "dashboards" property with host dashboards.
72	 * @param string|array  $options['selectInterfaces']                   Return an "interfaces" property with host interfaces.
73	 * @param string|array  $options['selectInventory']                    Return an "inventory" property with host inventory data.
74	 * @param string|array  $options['selectHttpTests']                    Return an "httpTests" property with host web scenarios.
75	 * @param string|array  $options['selectDiscoveryRule']                Return a "discoveryRule" property with the low-level discovery rule that created the host (from host prototype in VMware monitoring).
76	 * @param string|array  $options['selectHostDiscovery']                Return a "hostDiscovery" property with host discovery object data.
77	 * @param string|array  $options['selectTags']                         Return a "tags" property with host tags.
78	 * @param string|array  $options['selectInheritedTags']                Return an "inheritedTags" property with tags that are on templates which are linked to host.
79	 * @param bool          $options['countOutput']                        Return host count as output.
80	 * @param bool          $options['groupCount']                         Group the host count.
81	 * @param bool          $options['preservekeys']                       Return host IDs as array keys.
82	 * @param string        $options['sortfield']                          Field to sort by.
83	 * @param string        $options['sortorder']                          Sort order.
84	 * @param int           $options['limit']                              Limit selection.
85	 * @param int           $options['limitSelects']                       Limits the number of records returned by subselects.
86	 *
87	 * @return array|boolean Host data as array or false if error
88	 */
89	public function get($options = []) {
90		$result = [];
91
92		$sqlParts = [
93			'select'	=> ['hosts' => 'h.hostid'],
94			'from'		=> ['hosts' => 'hosts h'],
95			'where'		=> ['flags' => 'h.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'],
96			'group'		=> [],
97			'order'		=> [],
98			'limit'		=> null
99		];
100
101		$defOptions = [
102			'groupids'							=> null,
103			'hostids'							=> null,
104			'proxyids'							=> null,
105			'templateids'						=> null,
106			'interfaceids'						=> null,
107			'itemids'							=> null,
108			'triggerids'						=> null,
109			'maintenanceids'					=> null,
110			'graphids'							=> null,
111			'dserviceids'						=> null,
112			'httptestids'						=> null,
113			'monitored_hosts'					=> null,
114			'templated_hosts'					=> null,
115			'proxy_hosts'						=> null,
116			'with_items'						=> null,
117			'with_item_prototypes'				=> null,
118			'with_simple_graph_items'			=> null,
119			'with_simple_graph_item_prototypes'	=> null,
120			'with_monitored_items'				=> null,
121			'with_triggers'						=> null,
122			'with_monitored_triggers'			=> null,
123			'with_httptests'					=> null,
124			'with_monitored_httptests'			=> null,
125			'with_graphs'						=> null,
126			'with_graph_prototypes'				=> null,
127			'withProblemsSuppressed'			=> null,
128			'editable'							=> false,
129			'nopermissions'						=> null,
130			// filter
131			'evaltype'							=> TAG_EVAL_TYPE_AND_OR,
132			'tags'								=> null,
133			'severities'						=> null,
134			'inheritedTags'						=> false,
135			'filter'							=> null,
136			'search'							=> null,
137			'searchInventory'					=> null,
138			'searchByAny'						=> null,
139			'startSearch'						=> false,
140			'excludeSearch'						=> false,
141			'searchWildcardsEnabled'			=> false,
142			// output
143			'output'							=> API_OUTPUT_EXTEND,
144			'selectGroups'						=> null,
145			'selectParentTemplates'				=> null,
146			'selectItems'						=> null,
147			'selectDiscoveries'					=> null,
148			'selectTriggers'					=> null,
149			'selectGraphs'						=> null,
150			'selectMacros'						=> null,
151			'selectDashboards'					=> null,
152			'selectInterfaces'					=> null,
153			'selectInventory'					=> null,
154			'selectHttpTests'					=> null,
155			'selectDiscoveryRule'				=> null,
156			'selectHostDiscovery'				=> null,
157			'selectTags'						=> null,
158			'selectInheritedTags'				=> null,
159			'selectValueMaps'					=> null,
160			'countOutput'						=> false,
161			'groupCount'						=> false,
162			'preservekeys'						=> false,
163			'sortfield'							=> '',
164			'sortorder'							=> '',
165			'limit'								=> null,
166			'limitSelects'						=> null
167		];
168		$options = zbx_array_merge($defOptions, $options);
169
170		$this->validateGet($options);
171
172		// editable + PERMISSION CHECK
173		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) {
174			$permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ;
175			$userGroups = getUserGroupsByUserId(self::$userData['userid']);
176
177			$sqlParts['where'][] = 'EXISTS ('.
178					'SELECT NULL'.
179					' FROM hosts_groups hgg'.
180						' JOIN rights r'.
181							' ON r.id=hgg.groupid'.
182								' AND '.dbConditionInt('r.groupid', $userGroups).
183					' WHERE h.hostid=hgg.hostid'.
184					' GROUP BY hgg.hostid'.
185					' HAVING MIN(r.permission)>'.PERM_DENY.
186						' AND MAX(r.permission)>='.zbx_dbstr($permission).
187					')';
188		}
189
190		// hostids
191		if (!is_null($options['hostids'])) {
192			zbx_value2array($options['hostids']);
193			$sqlParts['where']['hostid'] = dbConditionInt('h.hostid', $options['hostids']);
194		}
195
196		// groupids
197		if (!is_null($options['groupids'])) {
198			zbx_value2array($options['groupids']);
199
200			$sqlParts['from']['hosts_groups'] = 'hosts_groups hg';
201			$sqlParts['where'][] = dbConditionInt('hg.groupid', $options['groupids']);
202			$sqlParts['where']['hgh'] = 'hg.hostid=h.hostid';
203
204			if ($options['groupCount']) {
205				$sqlParts['group']['groupid'] = 'hg.groupid';
206			}
207		}
208
209		// proxyids
210		if (!is_null($options['proxyids'])) {
211			zbx_value2array($options['proxyids']);
212
213			$sqlParts['where'][] = dbConditionId('h.proxy_hostid', $options['proxyids']);
214		}
215
216		// templateids
217		if (!is_null($options['templateids'])) {
218			zbx_value2array($options['templateids']);
219
220			$sqlParts['from']['hosts_templates'] = 'hosts_templates ht';
221			$sqlParts['where'][] = dbConditionInt('ht.templateid', $options['templateids']);
222			$sqlParts['where']['hht'] = 'h.hostid=ht.hostid';
223
224			if ($options['groupCount']) {
225				$sqlParts['group']['templateid'] = 'ht.templateid';
226			}
227		}
228
229		// interfaceids
230		if (!is_null($options['interfaceids'])) {
231			zbx_value2array($options['interfaceids']);
232
233			$sqlParts['left_join']['interface'] = ['alias' => 'hi', 'table' => 'interface', 'using' => 'hostid'];
234			$sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName];
235
236			$sqlParts['where'][] = dbConditionInt('hi.interfaceid', $options['interfaceids']);
237		}
238
239		// itemids
240		if (!is_null($options['itemids'])) {
241			zbx_value2array($options['itemids']);
242
243			$sqlParts['from']['items'] = 'items i';
244			$sqlParts['where'][] = dbConditionInt('i.itemid', $options['itemids']);
245			$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
246		}
247
248		// triggerids
249		if (!is_null($options['triggerids'])) {
250			zbx_value2array($options['triggerids']);
251
252			$sqlParts['from']['functions'] = 'functions f';
253			$sqlParts['from']['items'] = 'items i';
254			$sqlParts['where'][] = dbConditionInt('f.triggerid', $options['triggerids']);
255			$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
256			$sqlParts['where']['fi'] = 'f.itemid=i.itemid';
257		}
258
259		// httptestids
260		if (!is_null($options['httptestids'])) {
261			zbx_value2array($options['httptestids']);
262
263			$sqlParts['from']['httptest'] = 'httptest ht';
264			$sqlParts['where'][] = dbConditionInt('ht.httptestid', $options['httptestids']);
265			$sqlParts['where']['aht'] = 'ht.hostid=h.hostid';
266		}
267
268		// graphids
269		if (!is_null($options['graphids'])) {
270			zbx_value2array($options['graphids']);
271
272			$sqlParts['from']['graphs_items'] = 'graphs_items gi';
273			$sqlParts['from']['items'] = 'items i';
274			$sqlParts['where'][] = dbConditionInt('gi.graphid', $options['graphids']);
275			$sqlParts['where']['igi'] = 'i.itemid=gi.itemid';
276			$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
277		}
278
279		// dserviceids
280		if (!is_null($options['dserviceids'])) {
281			zbx_value2array($options['dserviceids']);
282
283			$sqlParts['from']['dservices'] = 'dservices ds';
284			$sqlParts['from']['interface'] = 'interface i';
285			$sqlParts['where'][] = dbConditionInt('ds.dserviceid', $options['dserviceids']);
286			$sqlParts['where']['dsh'] = 'ds.ip=i.ip';
287			$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
288
289			if ($options['groupCount']) {
290				$sqlParts['group']['dserviceid'] = 'ds.dserviceid';
291			}
292		}
293
294		// maintenanceids
295		if (!is_null($options['maintenanceids'])) {
296			zbx_value2array($options['maintenanceids']);
297
298			$sqlParts['from']['maintenances_hosts'] = 'maintenances_hosts mh';
299			$sqlParts['where'][] = dbConditionInt('mh.maintenanceid', $options['maintenanceids']);
300			$sqlParts['where']['hmh'] = 'h.hostid=mh.hostid';
301
302			if ($options['groupCount']) {
303				$sqlParts['group']['maintenanceid'] = 'mh.maintenanceid';
304			}
305		}
306
307		// monitored_hosts, templated_hosts
308		if (!is_null($options['monitored_hosts'])) {
309			$sqlParts['where']['status'] = 'h.status='.HOST_STATUS_MONITORED;
310		}
311		elseif (!is_null($options['templated_hosts'])) {
312			$sqlParts['where']['status'] = 'h.status IN ('.HOST_STATUS_MONITORED.','.HOST_STATUS_NOT_MONITORED.','.HOST_STATUS_TEMPLATE.')';
313		}
314		elseif (!is_null($options['proxy_hosts'])) {
315			$sqlParts['where']['status'] = 'h.status IN ('.HOST_STATUS_PROXY_ACTIVE.','.HOST_STATUS_PROXY_PASSIVE.')';
316		}
317		else {
318			$sqlParts['where']['status'] = 'h.status IN ('.HOST_STATUS_MONITORED.','.HOST_STATUS_NOT_MONITORED.')';
319		}
320
321		// with_items, with_simple_graph_items, with_monitored_items
322		if ($options['with_items'] !== null
323				|| $options['with_simple_graph_items'] !== null
324				|| $options['with_monitored_items'] !== null) {
325
326			if ($options['with_items'] !== null) {
327				$where_and =
328					' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED]);
329			}
330			elseif ($options['with_monitored_items'] !== null) {
331				$where_and =
332					' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED]).
333					' AND '.dbConditionInt('i.status', [ITEM_STATUS_ACTIVE]);
334			}
335			elseif ($options['with_simple_graph_items'] !== null) {
336				$where_and =
337					' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED]).
338					' AND '.dbConditionInt('i.status', [ITEM_STATUS_ACTIVE]).
339					' AND '.dbConditionInt('i.value_type', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64]);
340			}
341
342			$sqlParts['where'][] = 'EXISTS ('.
343				'SELECT NULL'.
344				' FROM items i'.
345				' WHERE h.hostid=i.hostid'.
346					$where_and.
347				')';
348		}
349
350		// with_item_prototypes, with_simple_graph_item_prototypes
351		if ($options['with_item_prototypes'] !== null || $options['with_simple_graph_item_prototypes'] !== null) {
352			if ($options['with_item_prototypes'] !== null) {
353				$where_and =
354					' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_PROTOTYPE]);
355			}
356			elseif ($options['with_simple_graph_item_prototypes'] !== null) {
357				$where_and =
358					' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_PROTOTYPE]).
359					' AND '.dbConditionInt('i.status', [ITEM_STATUS_ACTIVE]).
360					' AND '.dbConditionInt('i.value_type', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64]);
361			}
362
363			$sqlParts['where'][] = 'EXISTS ('.
364				'SELECT NULL'.
365				' FROM items i'.
366				' WHERE h.hostid=i.hostid'.
367					$where_and.
368				')';
369		}
370
371		// with_triggers, with_monitored_triggers
372		if (!is_null($options['with_triggers'])) {
373			$sqlParts['where'][] = 'EXISTS ('.
374					'SELECT NULL'.
375					' FROM items i,functions f,triggers t'.
376					' WHERE h.hostid=i.hostid'.
377						' AND i.itemid=f.itemid'.
378						' AND f.triggerid=t.triggerid'.
379						' AND t.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'.
380					')';
381		}
382		elseif (!is_null($options['with_monitored_triggers'])) {
383			$sqlParts['where'][] = 'EXISTS ('.
384					'SELECT NULL'.
385					' FROM items i,functions f,triggers t'.
386					' WHERE h.hostid=i.hostid'.
387						' AND i.itemid=f.itemid'.
388						' AND f.triggerid=t.triggerid'.
389						' AND i.status='.ITEM_STATUS_ACTIVE.
390						' AND t.status='.TRIGGER_STATUS_ENABLED.
391						' AND t.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'.
392					')';
393		}
394
395		// with_httptests, with_monitored_httptests
396		if (!empty($options['with_httptests'])) {
397			$sqlParts['where'][] = 'EXISTS (SELECT NULL FROM httptest ht WHERE ht.hostid=h.hostid)';
398		}
399		elseif (!empty($options['with_monitored_httptests'])) {
400			$sqlParts['where'][] = 'EXISTS ('.
401				'SELECT NULL'.
402				' FROM httptest ht'.
403				' WHERE h.hostid=ht.hostid'.
404					' AND ht.status='.HTTPTEST_STATUS_ACTIVE.
405				')';
406		}
407
408		// with_graphs
409		if ($options['with_graphs'] !== null) {
410			$sqlParts['where'][] = 'EXISTS ('.
411				'SELECT NULL'.
412				' FROM items i,graphs_items gi,graphs g'.
413				' WHERE i.hostid=h.hostid'.
414					' AND i.itemid=gi.itemid '.
415					' AND gi.graphid=g.graphid'.
416					' AND '.dbConditionInt('g.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED]).
417				')';
418		}
419
420		// with_graph_prototypes
421		if ($options['with_graph_prototypes'] !== null) {
422			$sqlParts['where'][] = 'EXISTS ('.
423				'SELECT NULL'.
424				' FROM items i,graphs_items gi,graphs g'.
425				' WHERE i.hostid=h.hostid'.
426					' AND i.itemid=gi.itemid '.
427					' AND gi.graphid=g.graphid'.
428					' AND '.dbConditionInt('g.flags', [ZBX_FLAG_DISCOVERY_PROTOTYPE]).
429				')';
430		}
431
432		// search
433		if (is_array($options['search'])) {
434			zbx_db_search('hosts h', $options, $sqlParts);
435
436			if (zbx_db_search('interface hi', $options, $sqlParts)) {
437				$sqlParts['left_join']['interface'] = ['alias' => 'hi', 'table' => 'interface', 'using' => 'hostid'];
438				$sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName];
439			}
440		}
441
442		// search inventory
443		if ($options['searchInventory'] !== null) {
444			$sqlParts['from']['host_inventory'] = 'host_inventory hii';
445			$sqlParts['where']['hii'] = 'h.hostid=hii.hostid';
446
447			zbx_db_search('host_inventory hii',
448				[
449					'search' => $options['searchInventory'],
450					'startSearch' => $options['startSearch'],
451					'excludeSearch' => $options['excludeSearch'],
452					'searchWildcardsEnabled' => $options['searchWildcardsEnabled'],
453					'searchByAny' => $options['searchByAny']
454				],
455				$sqlParts
456			);
457		}
458
459		// filter
460		if (is_array($options['filter'])) {
461			$this->dbFilter('hosts h', $options, $sqlParts);
462
463			if ($this->dbFilter('interface hi', $options, $sqlParts)) {
464				$sqlParts['left_join']['interface'] = ['alias' => 'hi', 'table' => 'interface', 'using' => 'hostid'];
465				$sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName];
466			}
467		}
468
469		// tags
470		if ($options['tags'] !== null && $options['tags']) {
471			if ($options['inheritedTags']) {
472				$sqlParts['left_join'][] = ['alias' => 'ht2', 'table' => 'hosts_templates', 'using' => 'hostid'];
473				$sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName];
474				$sqlParts['where'][] = CApiTagHelper::addInheritedHostTagsWhereCondition($options['tags'],
475					$options['evaltype']
476				);
477			}
478			else {
479				$sqlParts['where'][] = CApiTagHelper::addWhereCondition($options['tags'], $options['evaltype'], 'h',
480					'host_tag', 'hostid'
481				);
482			}
483		}
484
485		// limit
486		if (!zbx_ctype_digit($options['limit']) || !$options['limit']) {
487			$options['limit'] = null;
488		}
489
490		/*
491		 * Cleaning the output from write-only properties.
492		 */
493		$write_only_keys = ['tls_psk_identity', 'tls_psk'];
494
495		if ($options['output'] === API_OUTPUT_EXTEND) {
496			$all_keys = array_keys(DB::getSchema($this->tableName())['fields']);
497			$all_keys[] = 'inventory_mode';
498			$options['output'] = array_diff($all_keys, $write_only_keys);
499		}
500		/*
501		* For internal calls of API method, is possible to get the write-only fields if they were specified in output.
502		* Specify write-only fields in output only if they will not appear in debug mode.
503		*/
504		elseif (is_array($options['output']) && APP::getMode() === APP::EXEC_MODE_API) {
505			$options['output'] = array_diff($options['output'], $write_only_keys);
506		}
507
508		$sqlParts = $this->applyQueryFilterOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
509		$sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
510		$sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
511
512		// Return count or grouped counts via direct SQL count.
513		if ($options['countOutput'] && !$this->requiresPostSqlFiltering($options)) {
514			$res = DBselect(self::createSelectQueryFromParts($sqlParts), $options['limit']);
515			while ($host = DBfetch($res)) {
516				if ($options['groupCount']) {
517					$result[] = $host;
518				}
519				else {
520					$result = $host['rowscount'];
521				}
522			}
523
524			return $result;
525		}
526
527		$result = zbx_toHash($this->customFetch(self::createSelectQueryFromParts($sqlParts), $options), 'hostid');
528
529		// Return count for post SQL filtered result sets.
530		if ($options['countOutput']) {
531			return (string) count($result);
532		}
533
534		// Hosts share table with host prototypes. Therefore remove host unrelated fields.
535		if ($this->outputIsRequested('discover', $options['output'])) {
536			foreach ($result as &$row) {
537				unset($row['discover']);
538			}
539
540			unset($row);
541		}
542
543		if ($result) {
544			$result = $this->addRelatedObjects($options, $result);
545		}
546
547		// removing keys (hash -> array)
548		if (!$options['preservekeys']) {
549			$result = zbx_cleanHashes($result);
550		}
551
552		return $result;
553	}
554
555	protected function applyQueryFilterOptions($tableName, $tableAlias, array $options, array $sqlParts) {
556		if ($options['filter'] && array_key_exists('inventory_mode', $options['filter'])) {
557			if ($options['filter']['inventory_mode'] !== null) {
558				$inventory_mode_query = (array) $options['filter']['inventory_mode'];
559
560				$inventory_mode_where = [];
561				$null_position = array_search(HOST_INVENTORY_DISABLED, $inventory_mode_query);
562
563				if ($null_position !== false) {
564					unset($inventory_mode_query[$null_position]);
565					$inventory_mode_where[] = 'hinv.inventory_mode IS NULL';
566				}
567
568				if ($null_position === false || $inventory_mode_query) {
569					$inventory_mode_where[] = dbConditionInt('hinv.inventory_mode', $inventory_mode_query);
570				}
571
572				$sqlParts['where'][] = (count($inventory_mode_where) > 1)
573					? '('.implode(' OR ', $inventory_mode_where).')'
574					: $inventory_mode_where[0];
575			}
576		}
577
578		return $sqlParts;
579	}
580
581	protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) {
582		$sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts);
583
584		if (!$options['countOutput'] && $this->outputIsRequested('inventory_mode', $options['output'])) {
585			$sqlParts['select']['inventory_mode'] =
586				dbConditionCoalesce('hinv.inventory_mode', HOST_INVENTORY_DISABLED, 'inventory_mode');
587		}
588
589		if ((!$options['countOutput'] && $this->outputIsRequested('inventory_mode', $options['output']))
590				|| ($options['filter'] && array_key_exists('inventory_mode', $options['filter']))) {
591			$sqlParts['left_join'][] = ['alias' => 'hinv', 'table' => 'host_inventory', 'using' => 'hostid'];
592			$sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName];
593		}
594
595		return $sqlParts;
596	}
597
598	/**
599	 * Add host.
600	 *
601	 * @param array  $hosts                                 An array with hosts data.
602	 * @param string $hosts[]['host']                       Host technical name.
603	 * @param string $hosts[]['name']                       Host visible name (optional).
604	 * @param array  $hosts[]['groups']                     An array of host group objects with IDs that host will be
605	 *                                                      added to.
606	 * @param int    $hosts[]['status']                     Status of the host (optional).
607	 * @param array  $hosts[]['interfaces']                 An array of host interfaces data.
608	 * @param int    $hosts[]['interfaces']['type']         Interface type.
609	 * @param int    $hosts[]['interfaces']['main']         Is this the default interface to use.
610	 * @param string $hosts[]['interfaces']['ip']           Interface IP (optional).
611	 * @param int    $hosts[]['interfaces']['port']         Interface port (optional).
612	 * @param int    $hosts[]['interfaces']['useip']        Interface should use IP (optional).
613	 * @param string $hosts[]['interfaces']['dns']          Interface should use DNS (optional).
614	 * @param int    $hosts[]['interfaces']['details']      Interface additional fields (optional).
615	 * @param int    $hosts[]['proxy_hostid']               ID of the proxy that is used to monitor the host (optional).
616	 * @param int    $hosts[]['ipmi_authtype']              IPMI authentication type (optional).
617	 * @param int    $hosts[]['ipmi_privilege']             IPMI privilege (optional).
618	 * @param string $hosts[]['ipmi_username']              IPMI username (optional).
619	 * @param string $hosts[]['ipmi_password']              IPMI password (optional).
620	 * @param array  $hosts[]['tags']                       An array of tags (optional).
621	 * @param string $hosts[]['tags'][]['tag']              Tag name.
622	 * @param string $hosts[]['tags'][]['value']            Tag value.
623	 * @param array  $hosts[]['inventory']                  An array of host inventory data (optional).
624	 * @param array  $hosts[]['macros']                     An array of host macros (optional).
625	 * @param string $hosts[]['macros'][]['macro']          Host macro (required if "macros" is set).
626	 * @param array  $hosts[]['templates']                  An array of template objects with IDs that will be linked
627	 *                                                      to host (optional).
628	 * @param string $hosts[]['templates'][]['templateid']  Template ID (required if "templates" is set).
629	 * @param string $hosts[]['tls_connect']                Connections to host (optional).
630	 * @param string $hosts[]['tls_accept']                 Connections from host (optional).
631	 * @param string $hosts[]['tls_psk_identity']           PSK identity (required if "PSK" type is set).
632	 * @param string $hosts[]['tls_psk']                    PSK (required if "PSK" type is set).
633	 * @param string $hosts[]['tls_issuer']                 Certificate issuer (optional).
634	 * @param string $hosts[]['tls_subject']                Certificate subject (optional).
635	 *
636	 * @return array
637	 */
638	public function create($hosts) {
639		$this->validateCreate($hosts);
640
641		$hosts_groups = [];
642		$hosts_tags = [];
643		$hosts_interfaces = [];
644		$hosts_inventory = [];
645		$templates_hostids = [];
646
647		$hostids = DB::insert('hosts', $hosts);
648
649		foreach ($hosts as $index => &$host) {
650			$host['hostid'] = $hostids[$index];
651
652			foreach ($host['groups'] as $group) {
653				$hosts_groups[] = [
654					'hostid' => $host['hostid'],
655					'groupid' => $group['groupid']
656				];
657			}
658
659			if (array_key_exists('tags', $host)) {
660				foreach (zbx_toArray($host['tags']) as $tag) {
661					$hosts_tags[] = ['hostid' => $host['hostid']] + $tag;
662				}
663			}
664
665			if (array_key_exists('interfaces', $host)) {
666				foreach (zbx_toArray($host['interfaces']) as $interface) {
667					$hosts_interfaces[] = ['hostid' => $host['hostid']] + $interface;
668				}
669			}
670
671			if (array_key_exists('templates', $host)) {
672				foreach (zbx_toArray($host['templates']) as $template) {
673					$templates_hostids[$template['templateid']][] = $host['hostid'];
674				}
675			}
676
677			$host_inventory = [];
678			if (array_key_exists('inventory', $host) && $host['inventory']) {
679				$host_inventory = $host['inventory'];
680				$host_inventory['inventory_mode'] = HOST_INVENTORY_MANUAL;
681			}
682
683			if (array_key_exists('inventory_mode', $host) && $host['inventory_mode'] != HOST_INVENTORY_DISABLED) {
684				$host_inventory['inventory_mode'] = $host['inventory_mode'];
685			}
686
687			if (array_key_exists('inventory_mode', $host_inventory)) {
688				$hosts_inventory[] = ['hostid' => $host['hostid']] + $host_inventory;
689			}
690		}
691		unset($host);
692
693		DB::insertBatch('hosts_groups', $hosts_groups);
694
695		if ($hosts_tags) {
696			DB::insert('host_tag', $hosts_tags);
697		}
698
699		if ($hosts_interfaces) {
700			API::HostInterface()->create($hosts_interfaces);
701		}
702
703		$this->createHostMacros($hosts);
704
705		while ($templates_hostids) {
706			$templateid = key($templates_hostids);
707			$link_hostids = reset($templates_hostids);
708			$link_templateids = [$templateid];
709			unset($templates_hostids[$templateid]);
710
711			foreach ($templates_hostids as $templateid => $hostids) {
712				if ($link_hostids === $hostids) {
713					$link_templateids[] = $templateid;
714					unset($templates_hostids[$templateid]);
715				}
716			}
717
718			$this->link($link_templateids, $link_hostids);
719		}
720
721		if ($hosts_inventory) {
722			DB::insert('host_inventory', $hosts_inventory, false);
723		}
724
725		$this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_HOST, $hosts);
726
727		return ['hostids' => array_column($hosts, 'hostid')];
728	}
729
730	/**
731	 * Update host.
732	 *
733	 * @param array  $hosts                                       An array with hosts data.
734	 * @param string $hosts[]['hostid']                           Host ID.
735	 * @param string $hosts[]['host']                             Host technical name (optional).
736	 * @param string $hosts[]['name']                             Host visible name (optional).
737	 * @param array  $hosts[]['groups']                           An array of host group objects with IDs that host will be replaced to.
738	 * @param int    $hosts[]['status']                           Status of the host (optional).
739	 * @param array  $hosts[]['interfaces']                       An array of host interfaces data to be replaced.
740	 * @param int    $hosts[]['interfaces']['type']               Interface type.
741	 * @param int    $hosts[]['interfaces']['main']               Is this the default interface to use.
742	 * @param string $hosts[]['interfaces']['ip']                 Interface IP (optional).
743	 * @param int    $hosts[]['interfaces']['port']               Interface port (optional).
744	 * @param int    $hosts[]['interfaces']['useip']              Interface should use IP (optional).
745	 * @param string $hosts[]['interfaces']['dns']                Interface should use DNS (optional).
746	 * @param int    $hosts[]['interfaces']['details']            Interface additional fields (optional).
747	 * @param int    $hosts[]['proxy_hostid']                     ID of the proxy that is used to monitor the host (optional).
748	 * @param int    $hosts[]['ipmi_authtype']                    IPMI authentication type (optional).
749	 * @param int    $hosts[]['ipmi_privilege']                   IPMI privilege (optional).
750	 * @param string $hosts[]['ipmi_username']                    IPMI username (optional).
751	 * @param string $hosts[]['ipmi_password']                    IPMI password (optional).
752	 * @param array  $hosts[]['tags']                             An array of tags (optional).
753	 * @param string $hosts[]['tags'][]['tag']                    Tag name.
754	 * @param string $hosts[]['tags'][]['value']                  Tag value.
755	 * @param array  $hosts[]['inventory']                        An array of host inventory data (optional).
756	 * @param array  $hosts[]['macros']                           An array of host macros (optional).
757	 * @param string $hosts[]['macros'][]['macro']                Host macro (required if "macros" is set).
758	 * @param array  $hosts[]['templates']                        An array of template objects with IDs that will be linked to host (optional).
759	 * @param string $hosts[]['templates'][]['templateid']        Template ID (required if "templates" is set).
760	 * @param array  $hosts[]['templates_clear']                  Templates to unlink and clear from the host (optional).
761	 * @param string $hosts[]['templates_clear'][]['templateid']  Template ID (required if "templates" is set).
762	 * @param string $hosts[]['tls_connect']                      Connections to host (optional).
763	 * @param string $hosts[]['tls_accept']                       Connections from host (optional).
764	 * @param string $hosts[]['tls_psk_identity']                 PSK identity (required if "PSK" type is set).
765	 * @param string $hosts[]['tls_psk']                          PSK (required if "PSK" type is set).
766	 * @param string $hosts[]['tls_issuer']                       Certificate issuer (optional).
767	 * @param string $hosts[]['tls_subject']                      Certificate subject (optional).
768	 *
769	 * @return array
770	 */
771	public function update($hosts) {
772		$hosts = $this->validateUpdate($hosts, $db_hosts);
773
774		$inventories = [];
775		foreach ($hosts as &$host) {
776			// If visible name is not given or empty it should be set to host name.
777			if (array_key_exists('host', $host) && (!array_key_exists('name', $host) || !trim($host['name']))) {
778				$host['name'] = $host['host'];
779			}
780
781			// Fetch fields required to update host inventory.
782			if (array_key_exists('inventory', $host)) {
783				$inventory = $host['inventory'];
784				$inventory['hostid'] = $host['hostid'];
785
786				$inventories[] = $inventory;
787			}
788		}
789		unset($host);
790
791		$inventories = $this->extendObjects('host_inventory', $inventories, ['inventory_mode']);
792		$inventories = zbx_toHash($inventories, 'hostid');
793
794		$this->updateHostMacros($hosts, $db_hosts);
795
796		foreach ($hosts as &$host) {
797			unset($host['macros']);
798		}
799		unset($host);
800
801		$hosts = $this->extendObjectsByKey($hosts, $db_hosts, 'hostid', ['tls_connect', 'tls_accept', 'tls_issuer',
802			'tls_subject', 'tls_psk_identity', 'tls_psk'
803		]);
804
805		foreach ($hosts as $host) {
806			// Extend host inventory with the required data.
807			if (array_key_exists('inventory', $host) && $host['inventory']) {
808				// If inventory mode is HOST_INVENTORY_DISABLED, database record is not created.
809				if (array_key_exists('inventory_mode', $inventories[$host['hostid']])
810						&& ($inventories[$host['hostid']]['inventory_mode'] == HOST_INVENTORY_MANUAL
811							|| $inventories[$host['hostid']]['inventory_mode'] == HOST_INVENTORY_AUTOMATIC)) {
812					$host['inventory'] = $inventories[$host['hostid']];
813				}
814			}
815
816			$data = $host;
817			$data['hosts'] = ['hostid' => $host['hostid']];
818			$result = $this->massUpdate($data);
819
820			if (!$result) {
821				self::exception(ZBX_API_ERROR_INTERNAL, _('Host update failed.'));
822			}
823		}
824
825		$this->updateTags(array_column($hosts, 'tags', 'hostid'));
826
827		return ['hostids' => array_column($hosts, 'hostid')];
828	}
829
830	/**
831	 * Additionally allows to create new interfaces on hosts.
832	 *
833	 * Checks write permissions for hosts.
834	 *
835	 * Additional supported $data parameters are:
836	 * - interfaces - an array of interfaces to create on the hosts
837	 * - templates  - an array of templates to link to the hosts, overrides the CHostGeneral::massAdd()
838	 *                'templates' parameter
839	 *
840	 * @param array $data
841	 *
842	 * @return array
843	 */
844	public function massAdd(array $data) {
845		$hosts = isset($data['hosts']) ? zbx_toArray($data['hosts']) : [];
846		$hostIds = zbx_objectValues($hosts, 'hostid');
847
848		$this->checkPermissions($hostIds, _('You do not have permission to perform this operation.'));
849
850		// add new interfaces
851		if (!empty($data['interfaces'])) {
852			API::HostInterface()->massAdd([
853				'hosts' => $data['hosts'],
854				'interfaces' => zbx_toArray($data['interfaces'])
855			]);
856		}
857
858		// rename the "templates" parameter to the common "templates_link"
859		if (isset($data['templates'])) {
860			$data['templates_link'] = $data['templates'];
861			unset($data['templates']);
862		}
863
864		$data['templates'] = [];
865
866		return parent::massAdd($data);
867	}
868
869	/**
870	 * Mass update hosts.
871	 *
872	 * @param array  $hosts								multidimensional array with Hosts data
873	 * @param array  $hosts['hosts']					Array of Host objects to update
874	 * @param string $hosts['fields']['host']			Host name.
875	 * @param array  $hosts['fields']['groupids']		HostGroup IDs add Host to.
876	 * @param int    $hosts['fields']['port']			Port. OPTIONAL
877	 * @param int    $hosts['fields']['status']			Host Status. OPTIONAL
878	 * @param int    $hosts['fields']['useip']			Use IP. OPTIONAL
879	 * @param string $hosts['fields']['dns']			DNS. OPTIONAL
880	 * @param string $hosts['fields']['ip']				IP. OPTIONAL
881	 * @param int    $hosts['fields']['details']		Details. OPTIONAL
882	 * @param int    $hosts['fields']['proxy_hostid']	Proxy Host ID. OPTIONAL
883	 * @param int    $hosts['fields']['ipmi_authtype']	IPMI authentication type. OPTIONAL
884	 * @param int    $hosts['fields']['ipmi_privilege']	IPMI privilege. OPTIONAL
885	 * @param string $hosts['fields']['ipmi_username']	IPMI username. OPTIONAL
886	 * @param string $hosts['fields']['ipmi_password']	IPMI password. OPTIONAL
887	 *
888	 * @return boolean
889	 */
890	public function massUpdate($data) {
891		if (!array_key_exists('hosts', $data) || !is_array($data['hosts'])) {
892			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Field "%1$s" is mandatory.', 'hosts'));
893		}
894
895		$hosts = zbx_toArray($data['hosts']);
896		$inputHostIds = zbx_objectValues($hosts, 'hostid');
897		$hostids = array_unique($inputHostIds);
898
899		sort($hostids);
900
901		$db_hosts = $this->get([
902			'output' => ['hostid', 'proxy_hostid', 'host', 'status', 'ipmi_authtype', 'ipmi_privilege', 'ipmi_username',
903				'ipmi_password', 'name', 'description', 'tls_connect', 'tls_accept', 'tls_issuer', 'tls_subject',
904				'tls_psk_identity', 'tls_psk', 'inventory_mode'
905			],
906			'hostids' => $hostids,
907			'editable' => true,
908			'preservekeys' => true
909		]);
910
911		foreach ($hosts as $host) {
912			if (!array_key_exists($host['hostid'], $db_hosts)) {
913				self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.'));
914			}
915		}
916
917		// Check inventory mode value.
918		if (array_key_exists('inventory_mode', $data)) {
919			$valid_inventory_modes = [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC];
920			$inventory_mode = new CLimitedSetValidator([
921				'values' => $valid_inventory_modes,
922				'messageInvalid' => _s('Incorrect value for field "%1$s": %2$s.', 'inventory_mode',
923					_s('value must be one of %1$s', implode(', ', $valid_inventory_modes)))
924			]);
925			$this->checkValidator($data['inventory_mode'], $inventory_mode);
926		}
927
928		// Check connection fields only for massupdate action.
929		if (array_key_exists('tls_connect', $data) || array_key_exists('tls_accept', $data)
930				|| array_key_exists('tls_psk_identity', $data) || array_key_exists('tls_psk', $data)
931				|| array_key_exists('tls_issuer', $data) || array_key_exists('tls_subject', $data)) {
932			if (!array_key_exists('tls_connect', $data) || !array_key_exists('tls_accept', $data)) {
933				self::exception(ZBX_API_ERROR_PERMISSIONS, _(
934					'Cannot update host encryption settings. Connection settings for both directions should be specified.'
935				));
936			}
937
938			// Clean PSK fields.
939			if ($data['tls_connect'] != HOST_ENCRYPTION_PSK && !($data['tls_accept'] & HOST_ENCRYPTION_PSK)) {
940				$data['tls_psk_identity'] = '';
941				$data['tls_psk'] = '';
942			}
943
944			// Clean certificate fields.
945			if ($data['tls_connect'] != HOST_ENCRYPTION_CERTIFICATE
946					&& !($data['tls_accept'] & HOST_ENCRYPTION_CERTIFICATE)) {
947				$data['tls_issuer'] = '';
948				$data['tls_subject'] = '';
949			}
950		}
951
952		$this->validateEncryption([$data]);
953
954		if (array_key_exists('groups', $data) && !$data['groups'] && $db_hosts) {
955			$host = reset($db_hosts);
956
957			self::exception(ZBX_API_ERROR_PARAMETERS,
958				_s('Host "%1$s" cannot be without host group.', $host['host'])
959			);
960		}
961
962		// Property 'auto_compress' is not supported for hosts.
963		if (array_key_exists('auto_compress', $data)) {
964			self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.'));
965		}
966
967		/*
968		 * Update hosts properties
969		 */
970		if (isset($data['name'])) {
971			if (count($hosts) > 1) {
972				self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot mass update visible host name.'));
973			}
974		}
975
976		if (array_key_exists('host', $data)) {
977			$host_name_parser = new CHostNameParser();
978
979			if ($host_name_parser->parse($data['host']) != CParser::PARSE_SUCCESS) {
980				self::exception(ZBX_API_ERROR_PARAMETERS,
981					_s('Incorrect characters used for host name "%1$s".', $data['host'])
982				);
983			}
984
985			if (count($hosts) > 1) {
986				self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot mass update host name.'));
987			}
988
989			$curHost = reset($hosts);
990
991			$sameHostnameHost = $this->get([
992				'output' => ['hostid'],
993				'filter' => ['host' => $data['host']],
994				'nopermissions' => true,
995				'limit' => 1
996			]);
997			$sameHostnameHost = reset($sameHostnameHost);
998			if ($sameHostnameHost && (bccomp($sameHostnameHost['hostid'], $curHost['hostid']) != 0)) {
999				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Host "%1$s" already exists.', $data['host']));
1000			}
1001
1002			// can't add host with the same name as existing template
1003			$sameHostnameTemplate = API::Template()->get([
1004				'output' => ['templateid'],
1005				'filter' => ['host' => $data['host']],
1006				'nopermissions' => true,
1007				'limit' => 1
1008			]);
1009			if ($sameHostnameTemplate) {
1010				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Template "%1$s" already exists.', $data['host']));
1011			}
1012		}
1013
1014		if (isset($data['groups'])) {
1015			$updateGroups = $data['groups'];
1016		}
1017
1018		if (isset($data['interfaces'])) {
1019			$updateInterfaces = $data['interfaces'];
1020		}
1021
1022		if (array_key_exists('templates_clear', $data)) {
1023			$updateTemplatesClear = zbx_toArray($data['templates_clear']);
1024		}
1025
1026		if (isset($data['templates'])) {
1027			$updateTemplates = $data['templates'];
1028		}
1029
1030		if (isset($data['macros'])) {
1031			$updateMacros = $data['macros'];
1032		}
1033
1034		// second check is necessary, because import incorrectly inputs unset 'inventory' as empty string rather than null
1035		if (isset($data['inventory']) && $data['inventory']) {
1036			if (isset($data['inventory_mode']) && $data['inventory_mode'] == HOST_INVENTORY_DISABLED) {
1037				self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot set inventory fields for disabled inventory.'));
1038			}
1039
1040			$updateInventory = $data['inventory'];
1041			$updateInventory['inventory_mode'] = null;
1042		}
1043
1044		if (isset($data['inventory_mode'])) {
1045			if (!isset($updateInventory)) {
1046				$updateInventory = [];
1047			}
1048			$updateInventory['inventory_mode'] = $data['inventory_mode'];
1049		}
1050
1051		unset($data['hosts'], $data['groups'], $data['interfaces'], $data['templates_clear'], $data['templates'],
1052			$data['macros'], $data['inventory'], $data['inventory_mode']);
1053
1054		if (!zbx_empty($data)) {
1055			DB::update('hosts', [
1056				'values' => $data,
1057				'where' => ['hostid' => $hostids]
1058			]);
1059		}
1060
1061		/*
1062		 * Update template linkage
1063		 */
1064		if (isset($updateTemplatesClear)) {
1065			$templateIdsClear = zbx_objectValues($updateTemplatesClear, 'templateid');
1066
1067			if ($updateTemplatesClear) {
1068				$this->massRemove(['hostids' => $hostids, 'templateids_clear' => $templateIdsClear]);
1069			}
1070		}
1071		else {
1072			$templateIdsClear = [];
1073		}
1074
1075		// unlink templates
1076		if (isset($updateTemplates)) {
1077			$hostTemplates = API::Template()->get([
1078				'hostids' => $hostids,
1079				'output' => ['templateid'],
1080				'preservekeys' => true
1081			]);
1082
1083			$hostTemplateids = array_keys($hostTemplates);
1084			$newTemplateids = zbx_objectValues($updateTemplates, 'templateid');
1085
1086			$templatesToDel = array_diff($hostTemplateids, $newTemplateids);
1087			$templatesToDel = array_diff($templatesToDel, $templateIdsClear);
1088
1089			if ($templatesToDel) {
1090				$result = $this->massRemove([
1091					'hostids' => $hostids,
1092					'templateids' => $templatesToDel
1093				]);
1094				if (!$result) {
1095					self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot unlink template'));
1096				}
1097			}
1098		}
1099
1100		/*
1101		 * update interfaces
1102		 */
1103		if (isset($updateInterfaces)) {
1104			foreach($hostids as $hostid) {
1105				API::HostInterface()->replaceHostInterfaces([
1106					'hostid' => $hostid,
1107					'interfaces' => $updateInterfaces
1108				]);
1109			}
1110		}
1111
1112		// link new templates
1113		if (isset($updateTemplates)) {
1114			$result = $this->massAdd([
1115				'hosts' => $hosts,
1116				'templates' => $updateTemplates
1117			]);
1118
1119			if (!$result) {
1120				self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot link template'));
1121			}
1122		}
1123
1124		// macros
1125		if (isset($updateMacros)) {
1126			DB::delete('hostmacro', ['hostid' => $hostids]);
1127
1128			$this->massAdd([
1129				'hosts' => $hosts,
1130				'macros' => $updateMacros
1131			]);
1132		}
1133
1134		/*
1135		 * Inventory
1136		 */
1137		if (isset($updateInventory)) {
1138			// disabling inventory
1139			if ($updateInventory['inventory_mode'] == HOST_INVENTORY_DISABLED) {
1140				$sql = 'DELETE FROM host_inventory WHERE '.dbConditionInt('hostid', $hostids);
1141				if (!DBexecute($sql)) {
1142					self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot delete inventory.'));
1143				}
1144			}
1145			// changing inventory mode or setting inventory fields
1146			else {
1147				$existingInventoriesDb = DBfetchArrayAssoc(DBselect(
1148					'SELECT hostid,inventory_mode'.
1149					' FROM host_inventory'.
1150					' WHERE '.dbConditionInt('hostid', $hostids)
1151				), 'hostid');
1152
1153				// check existing host inventory data
1154				$automaticHostIds = [];
1155				if ($updateInventory['inventory_mode'] === null) {
1156					foreach ($hostids as $hostid) {
1157						// if inventory is disabled for one of the updated hosts, throw an exception
1158						if (!isset($existingInventoriesDb[$hostid])) {
1159							$host = get_host_by_hostid($hostid);
1160							self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1161								'Inventory disabled for host "%1$s".', $host['host']
1162							));
1163						}
1164						// if inventory mode is set to automatic, save its ID for later usage
1165						elseif ($existingInventoriesDb[$hostid]['inventory_mode'] == HOST_INVENTORY_AUTOMATIC) {
1166							$automaticHostIds[] = $hostid;
1167						}
1168					}
1169				}
1170
1171				$inventoriesToSave = [];
1172				foreach ($hostids as $hostid) {
1173					$hostInventory = $updateInventory;
1174					$hostInventory['hostid'] = $hostid;
1175
1176					// if no 'inventory_mode' has been passed, set inventory 'inventory_mode' from DB
1177					if ($updateInventory['inventory_mode'] === null) {
1178						$hostInventory['inventory_mode'] = $existingInventoriesDb[$hostid]['inventory_mode'];
1179					}
1180
1181					$inventoriesToSave[$hostid] = $hostInventory;
1182				}
1183
1184				// when updating automatic inventory, ignore fields that have items linked to them
1185				if ($updateInventory['inventory_mode'] == HOST_INVENTORY_AUTOMATIC
1186						|| ($updateInventory['inventory_mode'] === null && $automaticHostIds)) {
1187
1188					$itemsToInventories = API::item()->get([
1189						'output' => ['inventory_link', 'hostid'],
1190						'hostids' => $automaticHostIds ? $automaticHostIds : $hostids,
1191						'nopermissions' => true
1192					]);
1193
1194					$inventoryFields = getHostInventories();
1195					foreach ($itemsToInventories as $hinv) {
1196						// 0 means 'no link'
1197						if ($hinv['inventory_link'] != 0) {
1198							$inventoryName = $inventoryFields[$hinv['inventory_link']]['db_field'];
1199							unset($inventoriesToSave[$hinv['hostid']][$inventoryName]);
1200						}
1201					}
1202				}
1203
1204				// save inventory data
1205				foreach ($inventoriesToSave as $inventory) {
1206					$hostid = $inventory['hostid'];
1207					if (isset($existingInventoriesDb[$hostid])) {
1208						DB::update('host_inventory', [
1209							'values' => $inventory,
1210							'where' => ['hostid' => $hostid]
1211						]);
1212					}
1213					else {
1214						DB::insert('host_inventory', [$inventory], false);
1215					}
1216				}
1217			}
1218		}
1219
1220		/*
1221		 * Update host and host group linkage. This procedure should be done the last because user can unlink
1222		 * him self from a group with write permissions leaving only read premissions. Thus other procedures, like
1223		 * host-template linkage, inventory update, macros update, must be done before this.
1224		 */
1225		if (isset($updateGroups)) {
1226			$updateGroups = zbx_toArray($updateGroups);
1227
1228			$hostGroups = API::HostGroup()->get([
1229				'output' => ['groupid'],
1230				'hostids' => $hostids
1231			]);
1232			$hostGroupIds = zbx_objectValues($hostGroups, 'groupid');
1233			$newGroupIds = zbx_objectValues($updateGroups, 'groupid');
1234
1235			$groupsToAdd = array_diff($newGroupIds, $hostGroupIds);
1236			if ($groupsToAdd) {
1237				$this->massAdd([
1238					'hosts' => $hosts,
1239					'groups' => zbx_toObject($groupsToAdd, 'groupid')
1240				]);
1241			}
1242
1243			$groupIdsToDelete = array_diff($hostGroupIds, $newGroupIds);
1244			if ($groupIdsToDelete) {
1245				$this->massRemove([
1246					'hostids' => $hostids,
1247					'groupids' => $groupIdsToDelete
1248				]);
1249			}
1250		}
1251
1252		$new_hosts = [];
1253		foreach ($db_hosts as $hostid => $db_host) {
1254			$new_host = $data + $db_host;
1255			if ($new_host['status'] != $db_host['status']) {
1256				info(_s('Updated status of host "%1$s".', $new_host['host']));
1257			}
1258
1259			$new_hosts[] = $new_host;
1260		}
1261
1262		$this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_HOST, $new_hosts, $db_hosts);
1263
1264		return ['hostids' => $inputHostIds];
1265	}
1266
1267	/**
1268	 * Additionally allows to remove interfaces from hosts.
1269	 *
1270	 * Checks write permissions for hosts.
1271	 *
1272	 * Additional supported $data parameters are:
1273	 * - interfaces  - an array of interfaces to delete from the hosts
1274	 *
1275	 * @throws APIException if the input is invalid.
1276	 *
1277	 * @param array $data
1278	 *
1279	 * @return array
1280	 */
1281	public function massRemove(array $data) {
1282		if (!array_key_exists('hostids', $data) || $data['hostids'] === null) {
1283			self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1284		}
1285
1286		$data['hostids'] = zbx_toArray($data['hostids']);
1287
1288		$this->checkPermissions($data['hostids'], _('No permissions to referred object or it does not exist!'));
1289
1290		if (isset($data['interfaces'])) {
1291			$options = [
1292				'hostids' => $data['hostids'],
1293				'interfaces' => zbx_toArray($data['interfaces'])
1294			];
1295			API::HostInterface()->massRemove($options);
1296		}
1297
1298		// rename the "templates" parameter to the common "templates_link"
1299		if (isset($data['templateids'])) {
1300			$data['templateids_link'] = $data['templateids'];
1301			unset($data['templateids']);
1302		}
1303
1304		$data['templateids'] = [];
1305
1306		return parent::massRemove($data);
1307	}
1308
1309	/**
1310	 * Validates the input parameters for the delete() method.
1311	 *
1312	 * @throws APIException if the input is invalid
1313	 *
1314	 * @param array $hostIds
1315	 * @param bool 	$nopermissions
1316	 */
1317	protected function validateDelete(array $hostIds, $nopermissions = false) {
1318		if (!$hostIds) {
1319			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
1320		}
1321
1322		if (!$nopermissions) {
1323			$this->checkPermissions($hostIds, _('No permissions to referred object or it does not exist!'));
1324		}
1325
1326		$this->validateDeleteCheckMaintenances($hostIds);
1327	}
1328
1329	/**
1330	 * Validates if hosts may be deleted, due to maintenance constrain.
1331	 *
1332	 * @throws APIException if a constrain failed
1333	 *
1334	 * @param array $hostids
1335	 */
1336	protected function validateDeleteCheckMaintenances(array $hostids) {
1337		$maintenance = DBfetch(DBselect(
1338			'SELECT m.name'.
1339			' FROM maintenances m'.
1340			' WHERE NOT EXISTS ('.
1341				'SELECT NULL'.
1342				' FROM maintenances_hosts mh'.
1343				' WHERE m.maintenanceid=mh.maintenanceid'.
1344					' AND '.dbConditionInt('mh.hostid', $hostids, true).
1345			')'.
1346				' AND NOT EXISTS ('.
1347					'SELECT NULL'.
1348					' FROM maintenances_groups mg'.
1349					' WHERE m.maintenanceid=mg.maintenanceid'.
1350				')'
1351		));
1352
1353		if ($maintenance) {
1354			self::exception(ZBX_API_ERROR_PARAMETERS, _n(
1355				'Cannot delete host because maintenance "%1$s" must contain at least one host or host group.',
1356				'Cannot delete selected hosts because maintenance "%1$s" must contain at least one host or host group.',
1357				$maintenance['name'],
1358				count($hostids)
1359			));
1360		}
1361	}
1362
1363	/**
1364	 * Delete Host.
1365	 *
1366	 * @param array	$hostIds
1367	 * @param bool	$nopermissions
1368	 *
1369	 * @return array
1370	 */
1371	public function delete(array $hostIds, $nopermissions = false) {
1372		$this->validateDelete($hostIds, $nopermissions);
1373
1374		// delete the discovery rules first
1375		$del_rules = API::DiscoveryRule()->get([
1376			'output' => [],
1377			'hostids' => $hostIds,
1378			'nopermissions' => true,
1379			'preservekeys' => true
1380		]);
1381		if ($del_rules) {
1382			CDiscoveryRuleManager::delete(array_keys($del_rules));
1383		}
1384
1385		// delete the items
1386		$del_items = API::Item()->get([
1387			'output' => [],
1388			'templateids' => $hostIds,
1389			'nopermissions' => true,
1390			'preservekeys' => true
1391		]);
1392		if ($del_items) {
1393			CItemManager::delete(array_keys($del_items));
1394		}
1395
1396		// delete web tests
1397		$delHttptests = [];
1398		$dbHttptests = get_httptests_by_hostid($hostIds);
1399		while ($dbHttptest = DBfetch($dbHttptests)) {
1400			$delHttptests[$dbHttptest['httptestid']] = $dbHttptest['httptestid'];
1401		}
1402		if (!empty($delHttptests)) {
1403			API::HttpTest()->delete($delHttptests, true);
1404		}
1405
1406		// delete host from maps
1407		if (!empty($hostIds)) {
1408			DB::delete('sysmaps_elements', [
1409				'elementtype' => SYSMAP_ELEMENT_TYPE_HOST,
1410				'elementid' => $hostIds
1411			]);
1412		}
1413
1414		// disable actions
1415		// actions from conditions
1416		$actionids = [];
1417		$sql = 'SELECT DISTINCT actionid'.
1418				' FROM conditions'.
1419				' WHERE conditiontype='.CONDITION_TYPE_HOST.
1420				' AND '.dbConditionString('value', $hostIds);
1421		$dbActions = DBselect($sql);
1422		while ($dbAction = DBfetch($dbActions)) {
1423			$actionids[$dbAction['actionid']] = $dbAction['actionid'];
1424		}
1425
1426		// actions from operations
1427		$sql = 'SELECT DISTINCT o.actionid'.
1428				' FROM operations o, opcommand_hst oh'.
1429				' WHERE o.operationid=oh.operationid'.
1430				' AND '.dbConditionInt('oh.hostid', $hostIds);
1431		$dbActions = DBselect($sql);
1432		while ($dbAction = DBfetch($dbActions)) {
1433			$actionids[$dbAction['actionid']] = $dbAction['actionid'];
1434		}
1435
1436		if (!empty($actionids)) {
1437			$update = [];
1438			$update[] = [
1439				'values' => ['status' => ACTION_STATUS_DISABLED],
1440				'where' => ['actionid' => $actionids]
1441			];
1442			DB::update('actions', $update);
1443		}
1444
1445		// delete action conditions
1446		DB::delete('conditions', [
1447			'conditiontype' => CONDITION_TYPE_HOST,
1448			'value' => $hostIds
1449		]);
1450
1451		// delete action operation commands
1452		$operationids = [];
1453		$sql = 'SELECT DISTINCT oh.operationid'.
1454				' FROM opcommand_hst oh'.
1455				' WHERE '.dbConditionInt('oh.hostid', $hostIds);
1456		$dbOperations = DBselect($sql);
1457		while ($dbOperation = DBfetch($dbOperations)) {
1458			$operationids[$dbOperation['operationid']] = $dbOperation['operationid'];
1459		}
1460
1461		DB::delete('opcommand_hst', [
1462			'hostid' => $hostIds
1463		]);
1464
1465		// delete empty operations
1466		$delOperationids = [];
1467		$sql = 'SELECT DISTINCT o.operationid'.
1468				' FROM operations o'.
1469				' WHERE '.dbConditionInt('o.operationid', $operationids).
1470				' AND NOT EXISTS(SELECT oh.opcommand_hstid FROM opcommand_hst oh WHERE oh.operationid=o.operationid)';
1471		$dbOperations = DBselect($sql);
1472		while ($dbOperation = DBfetch($dbOperations)) {
1473			$delOperationids[$dbOperation['operationid']] = $dbOperation['operationid'];
1474		}
1475
1476		DB::delete('operations', [
1477			'operationid' => $delOperationids
1478		]);
1479
1480		$db_hosts = API::Host()->get([
1481			'output' => ['hostid', 'name'],
1482			'hostids' => $hostIds,
1483			'nopermissions' => true
1484		]);
1485
1486		// delete host inventory
1487		DB::delete('host_inventory', ['hostid' => $hostIds]);
1488
1489		// delete host
1490		DB::delete('hosts', ['hostid' => $hostIds]);
1491
1492		// TODO: remove info from API
1493		foreach ($db_hosts as $db_host) {
1494			info(_s('Deleted: Host "%1$s".', $db_host['name']));
1495		}
1496
1497		$this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_HOST, $db_hosts);
1498
1499		return ['hostids' => $hostIds];
1500	}
1501
1502	/**
1503	 * Retrieves and adds additional requested data to the result set.
1504	 *
1505	 * @param array  $options
1506	 * @param array  $result
1507	 *
1508	 * @return array
1509	 */
1510	protected function addRelatedObjects(array $options, array $result) {
1511		$result = parent::addRelatedObjects($options, $result);
1512
1513		$hostids = array_keys($result);
1514
1515		// adding inventory
1516		if ($options['selectInventory'] !== null) {
1517			$inventory = API::getApiService()->select('host_inventory', [
1518				'output' => $options['selectInventory'],
1519				'filter' => ['hostid' => $hostids],
1520				'preservekeys' => true
1521			]);
1522
1523			$inventory = $this->unsetExtraFields($inventory, ['hostid', 'inventory_mode'], []);
1524			$relation_map = $this->createRelationMap($result, 'hostid', 'hostid');
1525			$result = $relation_map->mapOne($result, $inventory, 'inventory');
1526		}
1527
1528		// adding hostinterfaces
1529		if ($options['selectInterfaces'] !== null) {
1530			if ($options['selectInterfaces'] != API_OUTPUT_COUNT) {
1531				$interfaces = API::HostInterface()->get([
1532					'output' => $this->outputExtend($options['selectInterfaces'], ['hostid', 'interfaceid']),
1533					'hostids' => $hostids,
1534					'nopermissions' => true,
1535					'preservekeys' => true
1536				]);
1537
1538				// we need to order interfaces for proper linkage and viewing
1539				order_result($interfaces, 'interfaceid', ZBX_SORT_UP);
1540
1541				$relationMap = $this->createRelationMap($interfaces, 'hostid', 'interfaceid');
1542
1543				$interfaces = $this->unsetExtraFields($interfaces, ['hostid', 'interfaceid'],
1544					$options['selectInterfaces']
1545				);
1546				$result = $relationMap->mapMany($result, $interfaces, 'interfaces', $options['limitSelects']);
1547			}
1548			else {
1549				$interfaces = API::HostInterface()->get([
1550					'hostids' => $hostids,
1551					'nopermissions' => true,
1552					'countOutput' => true,
1553					'groupCount' => true
1554				]);
1555
1556				$interfaces = zbx_toHash($interfaces, 'hostid');
1557				foreach ($result as $hostid => $host) {
1558					$result[$hostid]['interfaces'] = array_key_exists($hostid, $interfaces)
1559						? $interfaces[$hostid]['rowscount']
1560						: '0';
1561				}
1562			}
1563		}
1564
1565		// Adding dashboards.
1566		if ($options['selectDashboards'] !== null) {
1567			[$hosts_templates, $templateids] = CApiHostHelper::getParentTemplates($hostids);
1568
1569			if ($options['selectDashboards'] != API_OUTPUT_COUNT) {
1570				$dashboards = API::TemplateDashboard()->get([
1571					'output' => $this->outputExtend($options['selectDashboards'], ['templateid']),
1572					'templateids' => $templateids
1573				]);
1574
1575				if (!is_null($options['limitSelects'])) {
1576					order_result($dashboards, 'name');
1577				}
1578
1579				foreach ($result as &$host) {
1580					foreach ($hosts_templates[$host['hostid']] as $templateid) {
1581						foreach ($dashboards as $dashboard) {
1582							if ($dashboard['templateid'] == $templateid) {
1583								$host['dashboards'][] = $dashboard;
1584							}
1585						}
1586					}
1587				}
1588				unset($host);
1589			}
1590			else {
1591				$dashboards = API::TemplateDashboard()->get([
1592					'templateids' => $templateids,
1593					'countOutput' => true,
1594					'groupCount' => true
1595				]);
1596
1597				foreach ($result as $hostid => $host) {
1598					$result[$hostid]['dashboards'] = 0;
1599
1600					foreach ($dashboards as $dashboard) {
1601						if (in_array($dashboard['templateid'], $hosts_templates[$hostid])) {
1602							$result[$hostid]['dashboards'] += $dashboard['rowscount'];
1603						}
1604					}
1605
1606					$result[$hostid]['dashboards'] = (string) $result[$hostid]['dashboards'];
1607				}
1608			}
1609		}
1610
1611		// adding discovery rule
1612		if ($options['selectDiscoveryRule'] !== null && $options['selectDiscoveryRule'] != API_OUTPUT_COUNT) {
1613			// discovered items
1614			$discoveryRules = DBFetchArray(DBselect(
1615				'SELECT hd.hostid,hd2.parent_itemid'.
1616					' FROM host_discovery hd,host_discovery hd2'.
1617					' WHERE '.dbConditionInt('hd.hostid', $hostids).
1618					' AND hd.parent_hostid=hd2.hostid'
1619			));
1620			$relationMap = $this->createRelationMap($discoveryRules, 'hostid', 'parent_itemid');
1621
1622			$discoveryRules = API::DiscoveryRule()->get([
1623				'output' => $options['selectDiscoveryRule'],
1624				'itemids' => $relationMap->getRelatedIds(),
1625				'preservekeys' => true
1626			]);
1627			$result = $relationMap->mapOne($result, $discoveryRules, 'discoveryRule');
1628		}
1629
1630		// adding host discovery
1631		if ($options['selectHostDiscovery'] !== null) {
1632			$hostDiscoveries = API::getApiService()->select('host_discovery', [
1633				'output' => $this->outputExtend($options['selectHostDiscovery'], ['hostid']),
1634				'filter' => ['hostid' => $hostids],
1635				'preservekeys' => true
1636			]);
1637			$relationMap = $this->createRelationMap($hostDiscoveries, 'hostid', 'hostid');
1638
1639			$hostDiscoveries = $this->unsetExtraFields($hostDiscoveries, ['hostid'],
1640				$options['selectHostDiscovery']
1641			);
1642			$result = $relationMap->mapOne($result, $hostDiscoveries, 'hostDiscovery');
1643		}
1644
1645		if ($options['selectInheritedTags'] !== null && $options['selectInheritedTags'] != API_OUTPUT_COUNT) {
1646			$hosts_templates = [];
1647			[$hosts_templates, $templateids] = CApiHostHelper::getParentTemplates($hostids);
1648
1649			$templates = API::Template()->get([
1650				'output' => [],
1651				'selectTags' => ['tag', 'value'],
1652				'templateids' => $templateids,
1653				'preservekeys' => true,
1654				'nopermissions' => true
1655			]);
1656
1657			// Set "inheritedTags" for each host.
1658			foreach ($result as &$host) {
1659				$tags = [];
1660
1661				// Get IDs and template tag values from previously stored variables.
1662				foreach ($hosts_templates[$host['hostid']] as $templateid) {
1663					foreach ($templates[$templateid]['tags'] as $tag) {
1664						foreach ($tags as $_tag) {
1665							// Skip tags with same name and value.
1666							if ($_tag['tag'] === $tag['tag'] && $_tag['value'] === $tag['value']) {
1667								continue 2;
1668							}
1669						}
1670						$tags[] = $tag;
1671					}
1672				}
1673
1674				$host['inheritedTags'] = $this->unsetExtraFields($tags, ['tag', 'value'],
1675					$options['selectInheritedTags']
1676				);
1677			}
1678		}
1679
1680		return $result;
1681	}
1682
1683	/**
1684	 * Validates the input parameters for the get() method.
1685	 *
1686	 * @param array $options
1687	 *
1688	 * @throws APIException if the input is invalid
1689	 */
1690	protected function validateGet(array $options) {
1691		// Validate input parameters.
1692		$api_input_rules = ['type' => API_OBJECT, 'fields' => [
1693			'inheritedTags' => ['type' => API_BOOLEAN, 'default' => false],
1694			'selectInheritedTags' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
1695			'severities' =>	[
1696				'type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE | API_NOT_EMPTY, 'in' => implode(',', range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1)), 'uniq' => true
1697			],
1698			'withProblemsSuppressed' =>		['type' => API_BOOLEAN, 'flags' => API_ALLOW_NULL],
1699			'selectValueMaps' =>			['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => 'valuemapid,name,mappings']
1700		]];
1701		$options_filter = array_intersect_key($options, $api_input_rules['fields']);
1702		if (!CApiInputValidator::validate($api_input_rules, $options_filter, '/', $error)) {
1703			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
1704		}
1705	}
1706
1707	/**
1708	 * Checks if all of the given hosts are available for writing.
1709	 *
1710	 * @throws APIException     if a host is not writable or does not exist
1711	 *
1712	 * @param array  $hostids
1713	 * @param string $error
1714	 */
1715	protected function checkPermissions(array $hostids, $error) {
1716		if ($hostids) {
1717			$hostids = array_unique($hostids);
1718
1719			$count = $this->get([
1720				'countOutput' => true,
1721				'hostids' => $hostids,
1722				'editable' => true
1723			]);
1724
1725			if ($count != count($hostids)) {
1726				self::exception(ZBX_API_ERROR_PERMISSIONS, $error);
1727			}
1728		}
1729	}
1730
1731	/**
1732	 * Validate connections from/to host and PSK fields.
1733	 *
1734	 * @param array  $hosts
1735	 * @param string $hosts[]['hostid']                    (optional if $db_hosts is null)
1736	 * @param int    $hosts[]['tls_connect']               (optionsl)
1737	 * @param int    $hosts[]['tls_accept']                (optional)
1738	 * @param string $hosts[]['tls_psk_identity']          (optional)
1739	 * @param string $hosts[]['tls_psk']                   (optional)
1740	 * @param string $hosts[]['tls_issuer']                (optional)
1741	 * @param string $hosts[]['tls_subject']               (optional)
1742	 * @param array  $db_hosts                             (optional)
1743	 * @param int    $hosts[<hostid>]['tls_connect']
1744	 * @param int    $hosts[<hostid>]['tls_accept']
1745	 * @param string $hosts[<hostid>]['tls_psk_identity']
1746	 * @param string $hosts[<hostid>]['tls_psk']
1747	 * @param string $hosts[<hostid>]['tls_issuer']
1748	 * @param string $hosts[<hostid>]['tls_subject']
1749	 *
1750	 * @throws APIException if incorrect encryption options.
1751	 */
1752	protected function validateEncryption(array $hosts, array $db_hosts = null) {
1753		$available_connect_types = [HOST_ENCRYPTION_NONE, HOST_ENCRYPTION_PSK, HOST_ENCRYPTION_CERTIFICATE];
1754		$min_accept_type = HOST_ENCRYPTION_NONE;
1755		$max_accept_type = HOST_ENCRYPTION_NONE | HOST_ENCRYPTION_PSK | HOST_ENCRYPTION_CERTIFICATE;
1756
1757		foreach ($hosts as $host) {
1758			foreach (['tls_connect', 'tls_accept'] as $field_name) {
1759				$$field_name = array_key_exists($field_name, $host)
1760					? $host[$field_name]
1761					: ($db_hosts !== null ? $db_hosts[$host['hostid']][$field_name] : HOST_ENCRYPTION_NONE);
1762			}
1763
1764			if (!in_array($tls_connect, $available_connect_types)) {
1765				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_connect',
1766					_s('unexpected value "%1$s"', $tls_connect)
1767				));
1768			}
1769
1770			if ($tls_accept < $min_accept_type || $tls_accept > $max_accept_type) {
1771				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_accept',
1772					_s('unexpected value "%1$s"', $tls_accept)
1773				));
1774			}
1775
1776			foreach (['tls_psk_identity', 'tls_psk', 'tls_issuer', 'tls_subject'] as $field_name) {
1777				$$field_name = array_key_exists($field_name, $host)
1778					? $host[$field_name]
1779					: ($db_hosts !== null ? $db_hosts[$host['hostid']][$field_name] : '');
1780			}
1781
1782			// PSK validation.
1783			if ($tls_connect == HOST_ENCRYPTION_PSK || ($tls_accept & HOST_ENCRYPTION_PSK)) {
1784				if ($tls_psk_identity === '') {
1785					self::exception(ZBX_API_ERROR_PARAMETERS,
1786						_s('Incorrect value for field "%1$s": %2$s.', 'tls_psk_identity', _('cannot be empty'))
1787					);
1788				}
1789
1790				if ($tls_psk === '') {
1791					self::exception(ZBX_API_ERROR_PARAMETERS,
1792						_s('Incorrect value for field "%1$s": %2$s.', 'tls_psk', _('cannot be empty'))
1793					);
1794				}
1795
1796				if (!preg_match('/^([0-9a-f]{2})+$/i', $tls_psk)) {
1797					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_psk',
1798						_('an even number of hexadecimal characters is expected')
1799					));
1800				}
1801
1802				if (strlen($tls_psk) < PSK_MIN_LEN) {
1803					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_psk',
1804						_s('minimum length is %1$s characters', PSK_MIN_LEN)
1805					));
1806				}
1807			}
1808			else {
1809				if ($tls_psk_identity !== '') {
1810					self::exception(ZBX_API_ERROR_PARAMETERS,
1811						_s('Incorrect value for field "%1$s": %2$s.', 'tls_psk_identity', _('should be empty'))
1812					);
1813				}
1814
1815				if ($tls_psk !== '') {
1816					self::exception(ZBX_API_ERROR_PARAMETERS,
1817						_s('Incorrect value for field "%1$s": %2$s.', 'tls_psk', _('should be empty'))
1818					);
1819				}
1820			}
1821
1822			// Certificate validation.
1823			if ($tls_connect != HOST_ENCRYPTION_CERTIFICATE && !($tls_accept & HOST_ENCRYPTION_CERTIFICATE)) {
1824				if ($tls_issuer !== '') {
1825					self::exception(ZBX_API_ERROR_PARAMETERS,
1826						_s('Incorrect value for field "%1$s": %2$s.', 'tls_issuer', _('should be empty'))
1827					);
1828				}
1829
1830				if ($tls_subject !== '') {
1831					self::exception(ZBX_API_ERROR_PARAMETERS,
1832						_s('Incorrect value for field "%1$s": %2$s.', 'tls_subject', _('should be empty'))
1833					);
1834				}
1835			}
1836		}
1837	}
1838
1839	/**
1840	 * Validates the input parameters for the create() method.
1841	 *
1842	 * @param array $hosts		hosts data array
1843	 *
1844	 * @throws APIException if the input is invalid.
1845	 */
1846	protected function validateCreate(array &$hosts) {
1847		$hosts = zbx_toArray($hosts);
1848
1849		if (!$hosts) {
1850			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
1851		}
1852
1853		$macro_rules = ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['macro']], 'fields' => [
1854			'macro' =>			['type' => API_USER_MACRO, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('hostmacro', 'macro')],
1855			'type' =>			['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET, ZBX_MACRO_TYPE_VAULT]), 'default' => ZBX_MACRO_TYPE_TEXT],
1856			'value' =>			['type' => API_MULTIPLE, 'flags' => API_REQUIRED, 'rules' => [
1857									['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET])], 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'value')],
1858									['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_VAULT])], 'type' => API_VAULT_SECRET, 'length' => DB::getFieldLength('hostmacro', 'value')]
1859			]],
1860			'description' =>	['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'description')]
1861		]];
1862
1863		$host_name_parser = new CHostNameParser();
1864
1865		$host_db_fields = ['host' => null];
1866
1867		$groupids = [];
1868
1869		foreach ($hosts as $index => &$host) {
1870			// Validate mandatory fields.
1871			if (!check_db_fields($host_db_fields, $host)) {
1872				self::exception(ZBX_API_ERROR_PARAMETERS,
1873					_s('Wrong fields for host "%1$s".', array_key_exists('host', $host) ? $host['host'] : '')
1874				);
1875			}
1876
1877			// Property 'auto_compress' is not supported for hosts.
1878			if (array_key_exists('auto_compress', $host)) {
1879				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.'));
1880			}
1881
1882			// Validate "host" field.
1883			if ($host_name_parser->parse($host['host']) != CParser::PARSE_SUCCESS) {
1884				self::exception(ZBX_API_ERROR_PARAMETERS,
1885					_s('Incorrect characters used for host name "%1$s".', $host['host'])
1886				);
1887			}
1888
1889			// If visible name is not given or empty it should be set to host name. Required for duplicate checks.
1890			if (!array_key_exists('name', $host) || trim($host['name']) === '') {
1891				$host['name'] = $host['host'];
1892			}
1893
1894			// Validate "groups" field.
1895			if (!array_key_exists('groups', $host) || !is_array($host['groups']) || !$host['groups']) {
1896				self::exception(ZBX_API_ERROR_PARAMETERS,
1897					_s('Host "%1$s" cannot be without host group.', $host['host'])
1898				);
1899			}
1900
1901			$host['groups'] = zbx_toArray($host['groups']);
1902
1903			foreach ($host['groups'] as $group) {
1904				if (!is_array($group) || (is_array($group) && !array_key_exists('groupid', $group))) {
1905					self::exception(ZBX_API_ERROR_PARAMETERS,
1906						_s('Incorrect value for field "%1$s": %2$s.', 'groups',
1907							_s('the parameter "%1$s" is missing', 'groupid')
1908						)
1909					);
1910				}
1911
1912				$groupids[$group['groupid']] = true;
1913			}
1914
1915			// Validate tags.
1916			if (array_key_exists('tags', $host)) {
1917				$this->validateTags($host);
1918			}
1919
1920			if (array_key_exists('macros', $host)) {
1921				if (!CApiInputValidator::validate($macro_rules, $host['macros'], '/'.($index + 1).'/macros', $error)) {
1922					self::exception(ZBX_API_ERROR_PARAMETERS, $error);
1923				}
1924			}
1925		}
1926		unset($host);
1927
1928		// Check for duplicate "host" and "name" fields.
1929		$duplicate = CArrayHelper::findDuplicate($hosts, 'host');
1930		if ($duplicate) {
1931			self::exception(ZBX_API_ERROR_PARAMETERS,
1932				_s('Duplicate host. Host with the same host name "%1$s" already exists in data.', $duplicate['host'])
1933			);
1934		}
1935
1936		$duplicate = CArrayHelper::findDuplicate($hosts, 'name');
1937		if ($duplicate) {
1938			self::exception(ZBX_API_ERROR_PARAMETERS,
1939				_s('Duplicate host. Host with the same visible name "%1$s" already exists in data.', $duplicate['name'])
1940			);
1941		}
1942
1943		// Validate permissions to host groups.
1944		$db_groups = $groupids
1945			? API::HostGroup()->get([
1946				'output' => ['groupid'],
1947				'groupids' => array_keys($groupids),
1948				'editable' => true,
1949				'preservekeys' => true
1950			])
1951			: [];
1952
1953		foreach ($hosts as $host) {
1954			foreach ($host['groups'] as $group) {
1955				if (!array_key_exists($group['groupid'], $db_groups)) {
1956					self::exception(ZBX_API_ERROR_PERMISSIONS,
1957						_('No permissions to referred object or it does not exist!')
1958					);
1959				}
1960			}
1961		}
1962
1963		$inventory_fields = zbx_objectValues(getHostInventories(), 'db_field');
1964
1965		$valid_inventory_modes = [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC];
1966		$inventory_mode = new CLimitedSetValidator([
1967			'values' => $valid_inventory_modes,
1968			'messageInvalid' => _s('Incorrect value for field "%1$s": %2$s.', 'inventory_mode',
1969				_s('value must be one of %1$s', implode(', ', $valid_inventory_modes)))
1970		]);
1971
1972		$status_validator = new CLimitedSetValidator([
1973			'values' => [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED],
1974			'messageInvalid' => _('Incorrect status for host "%1$s".')
1975		]);
1976
1977		$host_names = [];
1978
1979		foreach ($hosts as $host) {
1980			if (array_key_exists('interfaces', $host) && $host['interfaces'] !== null
1981					&& !is_array($host['interfaces'])) {
1982				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1983			}
1984
1985			if (array_key_exists('status', $host)) {
1986				$status_validator->setObjectName($host['host']);
1987				$this->checkValidator($host['status'], $status_validator);
1988			}
1989
1990			if (array_key_exists('inventory_mode', $host)) {
1991				$inventory_mode->setObjectName($host['host']);
1992				$this->checkValidator($host['inventory_mode'], $inventory_mode);
1993			}
1994
1995			if (array_key_exists('inventory', $host) && $host['inventory']) {
1996				if (array_key_exists('inventory_mode', $host) && $host['inventory_mode'] == HOST_INVENTORY_DISABLED) {
1997					self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot set inventory fields for disabled inventory.'));
1998				}
1999
2000				$fields = array_keys($host['inventory']);
2001				foreach ($fields as $field) {
2002					if (!in_array($field, $inventory_fields)) {
2003						self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect inventory field "%1$s".', $field));
2004					}
2005				}
2006			}
2007
2008			// Collect technical and visible names to check if they exist in hosts and templates.
2009			$host_names['host'][$host['host']] = true;
2010			$host_names['name'][$host['name']] = true;
2011		}
2012
2013		$filter = [
2014			'host' => array_keys($host_names['host']),
2015			'name' => array_keys($host_names['name'])
2016		];
2017
2018		$hosts_exists = $this->get([
2019			'output' => ['host', 'name'],
2020			'filter' => $filter,
2021			'searchByAny' => true,
2022			'nopermissions' => true
2023		]);
2024
2025		foreach ($hosts_exists as $host_exists) {
2026			if (array_key_exists($host_exists['host'], $host_names['host'])) {
2027				self::exception(ZBX_API_ERROR_PARAMETERS,
2028					_s('Host with the same name "%1$s" already exists.', $host_exists['host'])
2029				);
2030			}
2031
2032			if (array_key_exists($host_exists['name'], $host_names['name'])) {
2033				self::exception(ZBX_API_ERROR_PARAMETERS,
2034					_s('Host with the same visible name "%1$s" already exists.', $host_exists['name'])
2035				);
2036			}
2037		}
2038
2039		$templates_exists = API::Template()->get([
2040			'output' => ['host', 'name'],
2041			'filter' => $filter,
2042			'searchByAny' => true,
2043			'nopermissions' => true
2044		]);
2045
2046		foreach ($templates_exists as $template_exists) {
2047			if (array_key_exists($template_exists['host'], $host_names['host'])) {
2048				self::exception(ZBX_API_ERROR_PARAMETERS,
2049					_s('Template with the same name "%1$s" already exists.', $template_exists['host'])
2050				);
2051			}
2052
2053			if (array_key_exists($template_exists['name'], $host_names['name'])) {
2054				self::exception(ZBX_API_ERROR_PARAMETERS,
2055					_s('Template with the same visible name "%1$s" already exists.', $template_exists['name'])
2056				);
2057			}
2058		}
2059
2060		$this->validateEncryption($hosts);
2061	}
2062
2063	/**
2064	 * Validates the input parameters for the update() method.
2065	 *
2066	 * @param array $hosts			hosts data array
2067	 * @param array $db_hosts		db hosts data array
2068	 *
2069	 * @throws APIException if the input is invalid.
2070	 */
2071	protected function validateUpdate(array &$hosts, array &$db_hosts = null) {
2072		$hosts = zbx_toArray($hosts);
2073
2074		if (!$hosts) {
2075			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
2076		}
2077
2078		$macro_rules = ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['hostmacroid']], 'fields' => [
2079			'hostmacroid' =>	['type' => API_ID],
2080			'macro' =>			['type' => API_USER_MACRO, 'length' => DB::getFieldLength('hostmacro', 'macro')],
2081			'type' =>			['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET, ZBX_MACRO_TYPE_VAULT])],
2082			'value' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'value')],
2083			'description' =>	['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'description')]
2084		]];
2085
2086		$db_hosts = $this->get([
2087			'output' => ['hostid', 'host', 'flags', 'tls_connect', 'tls_accept', 'tls_issuer', 'tls_subject'],
2088			'hostids' => array_column($hosts, 'hostid'),
2089			'editable' => true,
2090			'preservekeys' => true
2091		]);
2092
2093		// Load existing values of PSK fields of hosts independently from APP mode.
2094		$hosts_psk_fields = DB::select($this->tableName(), [
2095			'output' => ['tls_psk_identity', 'tls_psk'],
2096			'hostids' => array_keys($db_hosts),
2097			'preservekeys' => true
2098		]);
2099
2100		foreach ($hosts_psk_fields as $hostid => $psk_fields) {
2101			$db_hosts[$hostid] += $psk_fields;
2102		}
2103
2104		$host_db_fields = ['hostid' => null];
2105
2106		foreach ($hosts as $index => &$host) {
2107			// Validate mandatory fields.
2108			if (!check_db_fields($host_db_fields, $host)) {
2109				self::exception(ZBX_API_ERROR_PARAMETERS,
2110					_s('Wrong fields for host "%1$s".', array_key_exists('host', $host) ? $host['host'] : '')
2111				);
2112			}
2113
2114			// Property 'auto_compress' is not supported for hosts.
2115			if (array_key_exists('auto_compress', $host)) {
2116				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.'));
2117			}
2118
2119			// Validate host permissions.
2120			if (!array_key_exists($host['hostid'], $db_hosts)) {
2121				self::exception(ZBX_API_ERROR_PARAMETERS, _(
2122					'No permissions to referred object or it does not exist!'
2123				));
2124			}
2125
2126			// Validate "groups" field.
2127			if (array_key_exists('groups', $host)) {
2128				if (!is_array($host['groups']) || !$host['groups']) {
2129					self::exception(ZBX_API_ERROR_PARAMETERS,
2130						_s('Host "%1$s" cannot be without host group.', $db_hosts[$host['hostid']]['host'])
2131					);
2132				}
2133
2134				$host['groups'] = zbx_toArray($host['groups']);
2135
2136				foreach ($host['groups'] as $group) {
2137					if (!is_array($group) || (is_array($group) && !array_key_exists('groupid', $group))) {
2138						self::exception(ZBX_API_ERROR_PARAMETERS,
2139							_s('Incorrect value for field "%1$s": %2$s.', 'groups',
2140								_s('the parameter "%1$s" is missing', 'groupid')
2141							)
2142						);
2143					}
2144				}
2145			}
2146			// Permissions to host groups is validated in massUpdate().
2147
2148			if (array_key_exists('macros', $host)) {
2149				if (!CApiInputValidator::validate($macro_rules, $host['macros'], '/'.($index + 1).'/macros', $error)) {
2150					self::exception(ZBX_API_ERROR_PARAMETERS, $error);
2151				}
2152			}
2153		}
2154		unset($host);
2155
2156		if (array_column($hosts, 'macros')) {
2157			$db_hosts = $this->getHostMacros($db_hosts);
2158			$hosts = $this->validateHostMacros($hosts, $db_hosts);
2159		}
2160
2161		$inventory_fields = zbx_objectValues(getHostInventories(), 'db_field');
2162
2163		$valid_inventory_modes = [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC];
2164		$inventory_mode = new CLimitedSetValidator([
2165			'values' => $valid_inventory_modes,
2166			'messageInvalid' => _s('Incorrect value for field "%1$s": %2$s.', 'inventory_mode',
2167				_s('value must be one of %1$s', implode(', ', $valid_inventory_modes)))
2168		]);
2169
2170		$status_validator = new CLimitedSetValidator([
2171			'values' => [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED],
2172			'messageInvalid' => _('Incorrect status for host "%1$s".')
2173		]);
2174
2175		$update_discovered_validator = new CUpdateDiscoveredValidator([
2176			'allowed' => ['hostid', 'status', 'inventory', 'description'],
2177			'messageAllowedField' => _('Cannot update "%2$s" for a discovered host "%1$s".')
2178		]);
2179
2180		$host_name_parser = new CHostNameParser();
2181
2182		$host_names = [];
2183
2184		foreach ($hosts as &$host) {
2185			$db_host = $db_hosts[$host['hostid']];
2186			$host_name = array_key_exists('host', $host) ? $host['host'] : $db_host['host'];
2187
2188			if (array_key_exists('status', $host)) {
2189				$status_validator->setObjectName($host_name);
2190				$this->checkValidator($host['status'], $status_validator);
2191			}
2192
2193			if (array_key_exists('inventory_mode', $host)) {
2194				$inventory_mode->setObjectName($host_name);
2195				$this->checkValidator($host['inventory_mode'], $inventory_mode);
2196			}
2197
2198			if (array_key_exists('inventory', $host) && $host['inventory']) {
2199				if (array_key_exists('inventory_mode', $host) && $host['inventory_mode'] == HOST_INVENTORY_DISABLED) {
2200					self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot set inventory fields for disabled inventory.'));
2201				}
2202
2203				$fields = array_keys($host['inventory']);
2204				foreach ($fields as $field) {
2205					if (!in_array($field, $inventory_fields)) {
2206						self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect inventory field "%1$s".', $field));
2207					}
2208				}
2209			}
2210
2211			// cannot update certain fields for discovered hosts
2212			$update_discovered_validator->setObjectName($host_name);
2213			$this->checkPartialValidator($host, $update_discovered_validator, $db_host);
2214
2215			if (array_key_exists('interfaces', $host) && $host['interfaces'] !== null
2216					&& !is_array($host['interfaces'])) {
2217				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
2218			}
2219
2220			if (array_key_exists('host', $host)) {
2221				if ($host_name_parser->parse($host['host']) != CParser::PARSE_SUCCESS) {
2222					self::exception(ZBX_API_ERROR_PARAMETERS,
2223						_s('Incorrect characters used for host name "%1$s".', $host['host'])
2224					);
2225				}
2226
2227				if (array_key_exists('host', $host_names) && array_key_exists($host['host'], $host_names['host'])) {
2228					self::exception(ZBX_API_ERROR_PARAMETERS,
2229						_s('Duplicate host. Host with the same host name "%1$s" already exists in data.', $host['host'])
2230					);
2231				}
2232
2233				$host_names['host'][$host['host']] = $host['hostid'];
2234			}
2235
2236			if (array_key_exists('name', $host)) {
2237				// if visible name is empty replace it with host name
2238				if (zbx_empty(trim($host['name']))) {
2239					if (!array_key_exists('host', $host)) {
2240						self::exception(ZBX_API_ERROR_PARAMETERS,
2241							_s('Visible name cannot be empty if host name is missing.')
2242						);
2243					}
2244					$host['name'] = $host['host'];
2245				}
2246
2247				if (array_key_exists('name', $host_names) && array_key_exists($host['name'], $host_names['name'])) {
2248					self::exception(ZBX_API_ERROR_PARAMETERS, _s(
2249						'Duplicate host. Host with the same visible name "%1$s" already exists in data.', $host['name'])
2250					);
2251				}
2252				$host_names['name'][$host['name']] = $host['hostid'];
2253			}
2254
2255			if (array_key_exists('tls_connect', $host) || array_key_exists('tls_accept', $host)) {
2256				$tls_connect = array_key_exists('tls_connect', $host) ? $host['tls_connect'] : $db_host['tls_connect'];
2257				$tls_accept = array_key_exists('tls_accept', $host) ? $host['tls_accept'] : $db_host['tls_accept'];
2258
2259				// Clean PSK fields.
2260				if ($tls_connect != HOST_ENCRYPTION_PSK && !($tls_accept & HOST_ENCRYPTION_PSK)) {
2261					if (!array_key_exists('tls_psk_identity', $host)) {
2262						$host['tls_psk_identity'] = '';
2263					}
2264					if (!array_key_exists('tls_psk', $host)) {
2265						$host['tls_psk'] = '';
2266					}
2267				}
2268
2269				// Clean certificate fields.
2270				if ($tls_connect != HOST_ENCRYPTION_CERTIFICATE && !($tls_accept & HOST_ENCRYPTION_CERTIFICATE)) {
2271					if (!array_key_exists('tls_issuer', $host)) {
2272						$host['tls_issuer'] = '';
2273					}
2274					if (!array_key_exists('tls_subject', $host)) {
2275						$host['tls_subject'] = '';
2276					}
2277				}
2278			}
2279
2280			// Validate tags.
2281			if (array_key_exists('tags', $host)) {
2282				$this->validateTags($host);
2283			}
2284		}
2285		unset($host);
2286
2287		if (array_key_exists('host', $host_names) || array_key_exists('name', $host_names)) {
2288			$filter = [];
2289
2290			if (array_key_exists('host', $host_names)) {
2291				$filter['host'] = array_keys($host_names['host']);
2292			}
2293
2294			if (array_key_exists('name', $host_names)) {
2295				$filter['name'] = array_keys($host_names['name']);
2296			}
2297
2298			$hosts_exists = $this->get([
2299				'output' => ['hostid', 'host', 'name'],
2300				'filter' => $filter,
2301				'searchByAny' => true,
2302				'nopermissions' => true,
2303				'preservekeys' => true
2304			]);
2305
2306			foreach ($hosts_exists as $host_exists) {
2307				if (array_key_exists('host', $host_names) && array_key_exists($host_exists['host'], $host_names['host'])
2308						&& bccomp($host_exists['hostid'], $host_names['host'][$host_exists['host']]) != 0) {
2309					self::exception(ZBX_API_ERROR_PARAMETERS,
2310						_s('Host with the same name "%1$s" already exists.', $host_exists['host'])
2311					);
2312				}
2313
2314				if (array_key_exists('name', $host_names) && array_key_exists($host_exists['name'], $host_names['name'])
2315						&& bccomp($host_exists['hostid'], $host_names['name'][$host_exists['name']]) != 0) {
2316					self::exception(ZBX_API_ERROR_PARAMETERS,
2317						_s('Host with the same visible name "%1$s" already exists.', $host_exists['name'])
2318					);
2319				}
2320			}
2321
2322			$templates_exists = API::Template()->get([
2323				'output' => ['hostid', 'host', 'name'],
2324				'filter' => $filter,
2325				'searchByAny' => true,
2326				'nopermissions' => true,
2327				'preservekeys' => true
2328			]);
2329
2330			foreach ($templates_exists as $template_exists) {
2331				if (array_key_exists('host', $host_names)
2332						&& array_key_exists($template_exists['host'], $host_names['host'])
2333						&& bccomp($template_exists['templateid'], $host_names['host'][$template_exists['host']]) != 0) {
2334					self::exception(ZBX_API_ERROR_PARAMETERS,
2335						_s('Template with the same name "%1$s" already exists.', $template_exists['host'])
2336					);
2337				}
2338
2339				if (array_key_exists('name', $host_names)
2340						&& array_key_exists($template_exists['name'], $host_names['name'])
2341						&& bccomp($template_exists['templateid'], $host_names['name'][$template_exists['name']]) != 0) {
2342					self::exception(ZBX_API_ERROR_PARAMETERS,
2343						_s('Template with the same visible name "%1$s" already exists.', $template_exists['name'])
2344					);
2345				}
2346			}
2347		}
2348
2349		$this->validateEncryption($hosts, $db_hosts);
2350
2351		return $hosts;
2352	}
2353
2354	protected function requiresPostSqlFiltering(array $options) {
2355		return ($options['severities'] !== null || $options['withProblemsSuppressed'] !== null);
2356	}
2357
2358	protected function applyPostSqlFiltering(array $hosts, array $options) {
2359		$hosts = zbx_toHash($hosts, 'hostid');
2360
2361		if ($options['severities'] !== null || $options['withProblemsSuppressed'] !== null) {
2362			$triggers = API::Trigger()->get([
2363				'output' => [],
2364				'selectHosts' => ['hostid'],
2365				'hostids' => zbx_objectValues($hosts, 'hostid'),
2366				'skipDependent' => true,
2367				'status' => TRIGGER_STATUS_ENABLED,
2368				'preservekeys' => true
2369			]);
2370
2371			$problems = API::Problem()->get([
2372				'output' => ['objectid'],
2373				'objectids' => array_keys($triggers),
2374				'source' => EVENT_SOURCE_TRIGGERS,
2375				'object' => EVENT_OBJECT_TRIGGER,
2376				'suppressed' => $options['withProblemsSuppressed'],
2377				'severities' => $options['severities']
2378			]);
2379
2380			if (!$problems) {
2381				return [];
2382			}
2383
2384			// Keys are the trigger ids, that have problems.
2385			$problem_triggers = array_flip(array_column($problems, 'objectid'));
2386
2387			// Hostids, with triggerids on them.
2388			$host_triggers = [];
2389			foreach ($triggers as $triggerid => $trigger) {
2390				foreach ($trigger['hosts'] as $trigger_host) {
2391					$host_triggers[$trigger_host['hostid']][$triggerid] = true;
2392				}
2393			}
2394
2395			foreach ($hosts as $key => $host) {
2396				$problems_found = false;
2397
2398				if (array_key_exists($host['hostid'], $host_triggers)) {
2399					foreach (array_keys($host_triggers[$host['hostid']]) as $host_trigger) {
2400						if (array_key_exists($host_trigger, $problem_triggers)) {
2401							$problems_found = true;
2402							break;
2403						}
2404					}
2405				}
2406
2407				if (!$problems_found) {
2408					unset($hosts[$key]);
2409				}
2410			}
2411		}
2412
2413		return $hosts;
2414	}
2415}
2416