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 *
25 * @package API
26 */
27class CHost extends CHostGeneral {
28
29	protected $sortColumns = ['hostid', 'host', 'name', 'status'];
30
31	/**
32	 * Get host data.
33	 *
34	 * @param array      $options
35	 * @param array      $options['groupids']                  HostGroup IDs
36	 * @param array      $options['hostids']                   Host IDs
37	 * @param bool       $options['monitored_hosts']           only monitored Hosts
38	 * @param bool       $options['templated_hosts']           include templates in result
39	 * @param bool       $options['with_items']                only with items
40	 * @param bool       $options['with_monitored_items']      only with monitored items
41	 * @param bool       $options['with_triggers']             only with triggers
42	 * @param bool       $options['with_monitored_triggers']   only with monitored triggers
43	 * @param bool       $options['with_httptests']            only with http tests
44	 * @param bool       $options['with_monitored_httptests']  only with monitored http tests
45	 * @param bool       $options['with_graphs']               only with graphs
46	 * @param bool       $options['editable']                  only with read-write permission. Ignored for SuperAdmins
47	 * @param bool       $options['selectGroups']              select HostGroups
48	 * @param bool       $options['selectItems']               select Items
49	 * @param bool       $options['selectTriggers']            select Triggers
50	 * @param bool       $options['selectGraphs']              select Graphs
51	 * @param bool       $options['selectApplications']        select Applications
52	 * @param bool       $options['selectMacros']              select Macros
53	 * @param bool|array $options['selectInventory']           select Inventory
54	 * @param bool       $options['withInventory']             select only hosts with inventory
55	 * @param int        $options['count']                     count Hosts, returned column name is rowscount
56	 * @param string     $options['pattern']                   search hosts by pattern in Host name
57	 * @param string     $options['extendPattern']             search hosts by pattern in Host name, ip and DNS
58	 * @param int        $options['limit']                     limit selection
59	 * @param string     $options['sortfield']                 field to sort by
60	 * @param string     $options['sortorder']                 sort order
61	 *
62	 * @return array|boolean Host data as array or false if error
63	 */
64	public function get($options = []) {
65		$result = [];
66
67		$sqlParts = [
68			'select'	=> ['hosts' => 'h.hostid'],
69			'from'		=> ['hosts' => 'hosts h'],
70			'where'		=> ['flags' => 'h.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'],
71			'group'		=> [],
72			'order'		=> [],
73			'limit'		=> null
74		];
75
76		$defOptions = [
77			'groupids'					=> null,
78			'hostids'					=> null,
79			'proxyids'					=> null,
80			'templateids'				=> null,
81			'interfaceids'				=> null,
82			'itemids'					=> null,
83			'triggerids'				=> null,
84			'maintenanceids'			=> null,
85			'graphids'					=> null,
86			'applicationids'			=> null,
87			'dserviceids'				=> null,
88			'httptestids'				=> null,
89			'monitored_hosts'			=> null,
90			'templated_hosts'			=> null,
91			'proxy_hosts'				=> null,
92			'with_items'				=> null,
93			'with_monitored_items'		=> null,
94			'with_simple_graph_items'	=> null,
95			'with_triggers'				=> null,
96			'with_monitored_triggers'	=> null,
97			'with_httptests'			=> null,
98			'with_monitored_httptests'	=> null,
99			'with_graphs'				=> null,
100			'with_applications'			=> null,
101			'withInventory'				=> null,
102			'editable'					=> false,
103			'nopermissions'				=> null,
104			// filter
105			'filter'					=> null,
106			'search'					=> null,
107			'searchInventory'			=> null,
108			'searchByAny'				=> null,
109			'startSearch'				=> null,
110			'excludeSearch'				=> null,
111			'searchWildcardsEnabled'	=> null,
112			// output
113			'output'					=> API_OUTPUT_EXTEND,
114			'selectGroups'				=> null,
115			'selectParentTemplates'		=> null,
116			'selectItems'				=> null,
117			'selectDiscoveries'			=> null,
118			'selectTriggers'			=> null,
119			'selectGraphs'				=> null,
120			'selectApplications'		=> null,
121			'selectMacros'				=> null,
122			'selectScreens'				=> null,
123			'selectInterfaces'			=> null,
124			'selectInventory'			=> null,
125			'selectHttpTests'           => null,
126			'selectDiscoveryRule'		=> null,
127			'selectHostDiscovery'		=> null,
128			'countOutput'				=> null,
129			'groupCount'				=> null,
130			'preservekeys'				=> null,
131			'sortfield'					=> '',
132			'sortorder'					=> '',
133			'limit'						=> null,
134			'limitSelects'				=> null
135		];
136		$options = zbx_array_merge($defOptions, $options);
137
138		// editable + PERMISSION CHECK
139		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) {
140			$permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ;
141			$userGroups = getUserGroupsByUserId(self::$userData['userid']);
142
143			$sqlParts['where'][] = 'EXISTS ('.
144					'SELECT NULL'.
145					' FROM hosts_groups hgg'.
146						' JOIN rights r'.
147							' ON r.id=hgg.groupid'.
148								' AND '.dbConditionInt('r.groupid', $userGroups).
149					' WHERE h.hostid=hgg.hostid'.
150					' GROUP BY hgg.hostid'.
151					' HAVING MIN(r.permission)>'.PERM_DENY.
152						' AND MAX(r.permission)>='.zbx_dbstr($permission).
153					')';
154		}
155
156		// hostids
157		if (!is_null($options['hostids'])) {
158			zbx_value2array($options['hostids']);
159			$sqlParts['where']['hostid'] = dbConditionInt('h.hostid', $options['hostids']);
160		}
161
162		// groupids
163		if (!is_null($options['groupids'])) {
164			zbx_value2array($options['groupids']);
165
166			$sqlParts['from']['hosts_groups'] = 'hosts_groups hg';
167			$sqlParts['where'][] = dbConditionInt('hg.groupid', $options['groupids']);
168			$sqlParts['where']['hgh'] = 'hg.hostid=h.hostid';
169
170			if (!is_null($options['groupCount'])) {
171				$sqlParts['group']['groupid'] = 'hg.groupid';
172			}
173		}
174
175		// proxyids
176		if (!is_null($options['proxyids'])) {
177			zbx_value2array($options['proxyids']);
178
179			$sqlParts['where'][] = dbConditionInt('h.proxy_hostid', $options['proxyids']);
180		}
181
182		// templateids
183		if (!is_null($options['templateids'])) {
184			zbx_value2array($options['templateids']);
185
186			$sqlParts['from']['hosts_templates'] = 'hosts_templates ht';
187			$sqlParts['where'][] = dbConditionInt('ht.templateid', $options['templateids']);
188			$sqlParts['where']['hht'] = 'h.hostid=ht.hostid';
189
190			if (!is_null($options['groupCount'])) {
191				$sqlParts['group']['templateid'] = 'ht.templateid';
192			}
193		}
194
195		// interfaceids
196		if (!is_null($options['interfaceids'])) {
197			zbx_value2array($options['interfaceids']);
198
199			$sqlParts['from']['interface'] = 'interface hi';
200			$sqlParts['where'][] = dbConditionInt('hi.interfaceid', $options['interfaceids']);
201			$sqlParts['where']['hi'] = 'h.hostid=hi.hostid';
202		}
203
204		// itemids
205		if (!is_null($options['itemids'])) {
206			zbx_value2array($options['itemids']);
207
208			$sqlParts['from']['items'] = 'items i';
209			$sqlParts['where'][] = dbConditionInt('i.itemid', $options['itemids']);
210			$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
211		}
212
213		// triggerids
214		if (!is_null($options['triggerids'])) {
215			zbx_value2array($options['triggerids']);
216
217			$sqlParts['from']['functions'] = 'functions f';
218			$sqlParts['from']['items'] = 'items i';
219			$sqlParts['where'][] = dbConditionInt('f.triggerid', $options['triggerids']);
220			$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
221			$sqlParts['where']['fi'] = 'f.itemid=i.itemid';
222		}
223
224		// httptestids
225		if (!is_null($options['httptestids'])) {
226			zbx_value2array($options['httptestids']);
227
228			$sqlParts['from']['httptest'] = 'httptest ht';
229			$sqlParts['where'][] = dbConditionInt('ht.httptestid', $options['httptestids']);
230			$sqlParts['where']['aht'] = 'ht.hostid=h.hostid';
231		}
232
233		// graphids
234		if (!is_null($options['graphids'])) {
235			zbx_value2array($options['graphids']);
236
237			$sqlParts['from']['graphs_items'] = 'graphs_items gi';
238			$sqlParts['from']['items'] = 'items i';
239			$sqlParts['where'][] = dbConditionInt('gi.graphid', $options['graphids']);
240			$sqlParts['where']['igi'] = 'i.itemid=gi.itemid';
241			$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
242		}
243
244		// applicationids
245		if (!is_null($options['applicationids'])) {
246			zbx_value2array($options['applicationids']);
247
248			$sqlParts['from']['applications'] = 'applications a';
249			$sqlParts['where'][] = dbConditionInt('a.applicationid', $options['applicationids']);
250			$sqlParts['where']['ah'] = 'a.hostid=h.hostid';
251		}
252
253		// dserviceids
254		if (!is_null($options['dserviceids'])) {
255			zbx_value2array($options['dserviceids']);
256
257			$sqlParts['from']['dservices'] = 'dservices ds';
258			$sqlParts['from']['interface'] = 'interface i';
259			$sqlParts['where'][] = dbConditionInt('ds.dserviceid', $options['dserviceids']);
260			$sqlParts['where']['dsh'] = 'ds.ip=i.ip';
261			$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
262
263			if (!is_null($options['groupCount'])) {
264				$sqlParts['group']['dserviceid'] = 'ds.dserviceid';
265			}
266		}
267
268		// maintenanceids
269		if (!is_null($options['maintenanceids'])) {
270			zbx_value2array($options['maintenanceids']);
271
272			$sqlParts['from']['maintenances_hosts'] = 'maintenances_hosts mh';
273			$sqlParts['where'][] = dbConditionInt('mh.maintenanceid', $options['maintenanceids']);
274			$sqlParts['where']['hmh'] = 'h.hostid=mh.hostid';
275
276			if (!is_null($options['groupCount'])) {
277				$sqlParts['group']['maintenanceid'] = 'mh.maintenanceid';
278			}
279		}
280
281		// monitored_hosts, templated_hosts
282		if (!is_null($options['monitored_hosts'])) {
283			$sqlParts['where']['status'] = 'h.status='.HOST_STATUS_MONITORED;
284		}
285		elseif (!is_null($options['templated_hosts'])) {
286			$sqlParts['where']['status'] = 'h.status IN ('.HOST_STATUS_MONITORED.','.HOST_STATUS_NOT_MONITORED.','.HOST_STATUS_TEMPLATE.')';
287		}
288		elseif (!is_null($options['proxy_hosts'])) {
289			$sqlParts['where']['status'] = 'h.status IN ('.HOST_STATUS_PROXY_ACTIVE.','.HOST_STATUS_PROXY_PASSIVE.')';
290		}
291		else {
292			$sqlParts['where']['status'] = 'h.status IN ('.HOST_STATUS_MONITORED.','.HOST_STATUS_NOT_MONITORED.')';
293		}
294
295		// with_items, with_monitored_items, with_simple_graph_items
296		if (!is_null($options['with_items'])) {
297			$sqlParts['where'][] = 'EXISTS ('.
298					'SELECT NULL'.
299					' FROM items i'.
300					' WHERE h.hostid=i.hostid'.
301						' AND i.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'.
302					')';
303		}
304		elseif (!is_null($options['with_monitored_items'])) {
305			$sqlParts['where'][] = 'EXISTS ('.
306					'SELECT NULL'.
307					' FROM items i'.
308					' WHERE h.hostid=i.hostid'.
309						' AND i.status='.ITEM_STATUS_ACTIVE.
310						' AND i.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'.
311					')';
312		}
313		elseif (!is_null($options['with_simple_graph_items'])) {
314			$sqlParts['where'][] = 'EXISTS ('.
315					'SELECT NULL'.
316					' FROM items i'.
317					' WHERE h.hostid=i.hostid'.
318						' AND i.value_type IN ('.ITEM_VALUE_TYPE_FLOAT.','.ITEM_VALUE_TYPE_UINT64.')'.
319						' AND i.status='.ITEM_STATUS_ACTIVE.
320						' AND i.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'.
321					')';
322		}
323
324		// with_triggers, with_monitored_triggers
325		if (!is_null($options['with_triggers'])) {
326			$sqlParts['where'][] = 'EXISTS ('.
327					'SELECT NULL'.
328					' FROM items i,functions f,triggers t'.
329					' WHERE h.hostid=i.hostid'.
330						' AND i.itemid=f.itemid'.
331						' AND f.triggerid=t.triggerid'.
332						' AND t.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'.
333					')';
334		}
335		elseif (!is_null($options['with_monitored_triggers'])) {
336			$sqlParts['where'][] = 'EXISTS ('.
337					'SELECT NULL'.
338					' FROM items i,functions f,triggers t'.
339					' WHERE h.hostid=i.hostid'.
340						' AND i.itemid=f.itemid'.
341						' AND f.triggerid=t.triggerid'.
342						' AND i.status='.ITEM_STATUS_ACTIVE.
343						' AND t.status='.TRIGGER_STATUS_ENABLED.
344						' AND t.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'.
345					')';
346		}
347
348		// with_httptests, with_monitored_httptests
349		if (!empty($options['with_httptests'])) {
350			$sqlParts['where'][] = 'EXISTS (SELECT NULL FROM httptest ht WHERE ht.hostid=h.hostid)';
351		}
352		elseif (!empty($options['with_monitored_httptests'])) {
353			$sqlParts['where'][] = 'EXISTS ('.
354				'SELECT NULL'.
355				' FROM httptest ht'.
356				' WHERE h.hostid=ht.hostid'.
357					' AND ht.status='.HTTPTEST_STATUS_ACTIVE.
358				')';
359		}
360
361		// with_graphs
362		if (!is_null($options['with_graphs'])) {
363			$sqlParts['where'][] = 'EXISTS ('.
364					'SELECT NULL'.
365					' FROM items i,graphs_items gi,graphs g'.
366					' WHERE i.hostid=h.hostid'.
367						' AND i.itemid=gi.itemid '.
368						' AND gi.graphid=g.graphid'.
369						' AND g.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'.
370					')';
371		}
372
373		// with applications
374		if (!is_null($options['with_applications'])) {
375			$sqlParts['from']['applications'] = 'applications a';
376			$sqlParts['where'][] = 'a.hostid=h.hostid';
377		}
378
379		// withInventory
380		if (!is_null($options['withInventory']) && $options['withInventory']) {
381			$sqlParts['where'][] = ' h.hostid IN ('.
382					' SELECT hin.hostid'.
383					' FROM host_inventory hin'.
384					')';
385		}
386
387		// search
388		if (is_array($options['search'])) {
389			zbx_db_search('hosts h', $options, $sqlParts);
390
391			if (zbx_db_search('interface hi', $options, $sqlParts)) {
392				$sqlParts['from']['interface'] = 'interface hi';
393				$sqlParts['where']['hi'] = 'h.hostid=hi.hostid';
394			}
395		}
396
397		// search inventory
398		if ($options['searchInventory'] !== null) {
399			$sqlParts['from']['host_inventory'] = 'host_inventory hii';
400			$sqlParts['where']['hii'] = 'h.hostid=hii.hostid';
401
402			zbx_db_search('host_inventory hii',
403				[
404					'search' => $options['searchInventory'],
405					'startSearch' => $options['startSearch'],
406					'excludeSearch' => $options['excludeSearch'],
407					'searchWildcardsEnabled' => $options['searchWildcardsEnabled'],
408					'searchByAny' => $options['searchByAny']
409				],
410				$sqlParts
411			);
412		}
413
414		// filter
415		if (is_array($options['filter'])) {
416			$this->dbFilter('hosts h', $options, $sqlParts);
417
418			if ($this->dbFilter('interface hi', $options, $sqlParts)) {
419				$sqlParts['from']['interface'] = 'interface hi';
420				$sqlParts['where']['hi'] = 'h.hostid=hi.hostid';
421			}
422		}
423
424		// limit
425		if (zbx_ctype_digit($options['limit']) && $options['limit']) {
426			$sqlParts['limit'] = $options['limit'];
427		}
428
429		$sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
430		$sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
431		$res = DBselect($this->createSelectQueryFromParts($sqlParts), $sqlParts['limit']);
432		while ($host = DBfetch($res)) {
433			if (!is_null($options['countOutput'])) {
434				if (!is_null($options['groupCount'])) {
435					$result[] = $host;
436				}
437				else {
438					$result = $host['rowscount'];
439				}
440			}
441			else {
442				$result[$host['hostid']] = $host;
443			}
444		}
445
446		if (!is_null($options['countOutput'])) {
447			return $result;
448		}
449
450		if ($result) {
451			$result = $this->addRelatedObjects($options, $result);
452		}
453
454		// removing keys (hash -> array)
455		if (is_null($options['preservekeys'])) {
456			$result = zbx_cleanHashes($result);
457		}
458
459		return $result;
460	}
461
462	/**
463	 * Add host.
464	 *
465	 * @param array  $hosts									An array with hosts data.
466	 * @param string $hosts[]['host']						Host technical name.
467	 * @param string $hosts[]['name']						Host visible name (optional).
468	 * @param array  $hosts[]['groups']						An array of host group objects with IDs that host will be added to.
469	 * @param int    $hosts[]['status']						Status of the host (optional).
470	 * @param array  $hosts[]['interfaces']					An array of host interfaces data.
471	 * @param int    $hosts[]['interfaces']['type']			Interface type.
472	 * @param int    $hosts[]['interfaces']['main']			Is this the default interface to use.
473	 * @param string $hosts[]['interfaces']['ip']			Interface IP (optional).
474	 * @param int    $hosts[]['interfaces']['port']			Interface port (optional).
475	 * @param int    $hosts[]['interfaces']['useip']		Interface shoud use IP (optional).
476	 * @param string $hosts[]['interfaces']['dns']			Interface shoud use DNS (optional).
477	 * @param int    $hosts[]['interfaces']['bulk']			Use bulk requests for interface (optional).
478	 * @param int    $hosts[]['proxy_hostid']				ID of the proxy that is used to monitor the host (optional).
479	 * @param int    $hosts[]['ipmi_authtype']				IPMI authentication type (optional).
480	 * @param int    $hosts[]['ipmi_privilege']				IPMI privilege (optional).
481	 * @param string $hosts[]['ipmi_username']				IPMI username (optional).
482	 * @param string $hosts[]['ipmi_password']				IPMI password (optional).
483	 * @param array  $hosts[]['inventory']					An array of host inventory data (optional).
484	 * @param array  $hosts[]['macros']						An array of host macros (optional).
485	 * @param string $hosts[]['macros'][]['macro']			Host macro (required if "macros" is set).
486	 * @param array  $hosts[]['templates']					An array of template objects with IDs that will be linked to host (optional).
487	 * @param string $hosts[]['templates'][]['templateid']	Template ID (required if "templates" is set).
488	 * @param string $hosts[]['tls_connect']				Connections to host (optional).
489	 * @param string $hosts[]['tls_accept']					Connections from host (optional).
490	 * @param string $hosts[]['tls_psk_identity']			PSK identity (required if "PSK" type is set).
491	 * @param string $hosts[]['tls_psk']					PSK (required if "PSK" type is set).
492	 * @param string $hosts[]['tls_issuer']					Certificate issuer (optional).
493	 * @param string $hosts[]['tls_subject']				Certificate subject (optional).
494	 *
495	 * @return array
496	 */
497	public function create($hosts) {
498		$hosts = zbx_toArray($hosts);
499
500		$this->validateCreate($hosts);
501
502		$hostids = [];
503
504		foreach ($hosts as $host) {
505			// If visible name is not given or empty it should be set to host name.
506			if (!array_key_exists('name', $host) || !trim($host['name'])) {
507				$host['name'] = $host['host'];
508			}
509
510			$hostid = DB::insert('hosts', [$host]);
511			$hostid = reset($hostid);
512			$host['hostid'] = $hostid;
513			$hostids[] = $hostid;
514
515			// Save groups. Groups must be added before calling massAdd() for permission validation to work.
516			$groupsToAdd = [];
517			foreach ($host['groups'] as $group) {
518				$groupsToAdd[] = [
519					'hostid' => $hostid,
520					'groupid' => $group['groupid']
521				];
522			}
523			DB::insert('hosts_groups', $groupsToAdd);
524
525			$options = [
526				'hosts' => $host
527			];
528
529			if (isset($host['templates']) && !is_null($host['templates'])) {
530				$options['templates'] = $host['templates'];
531			}
532
533			if (isset($host['macros']) && !is_null($host['macros'])) {
534				$options['macros'] = $host['macros'];
535			}
536
537			if (isset($host['interfaces']) && !is_null($host['interfaces'])) {
538				$options['interfaces'] = $host['interfaces'];
539			}
540
541			$result = API::Host()->massAdd($options);
542			if (!$result) {
543				self::exception();
544			}
545
546			if (array_key_exists('inventory', $host) && $host['inventory']) {
547				$hostInventory = $host['inventory'];
548				$hostInventory['inventory_mode'] = HOST_INVENTORY_MANUAL;
549			}
550			else {
551				$hostInventory = [];
552			}
553
554			if (array_key_exists('inventory_mode', $host) && $host['inventory_mode'] != HOST_INVENTORY_DISABLED) {
555				$hostInventory['inventory_mode'] = $host['inventory_mode'];
556			}
557
558			if (array_key_exists('inventory_mode', $hostInventory)
559					&& ($hostInventory['inventory_mode'] == HOST_INVENTORY_MANUAL
560						|| $hostInventory['inventory_mode'] == HOST_INVENTORY_AUTOMATIC)) {
561				$hostInventory['hostid'] = $hostid;
562				DB::insert('host_inventory', [$hostInventory], false);
563			}
564		}
565
566		return ['hostids' => $hostids];
567	}
568
569	/**
570	 * Update host.
571	 *
572	 * @param array  $hosts											An array with hosts data.
573	 * @param string $hosts[]['hostid']								Host ID.
574	 * @param string $hosts[]['host']								Host technical name (optional).
575	 * @param string $hosts[]['name']								Host visible name (optional).
576	 * @param array  $hosts[]['groups']								An array of host group objects with IDs that host will be replaced to.
577	 * @param int    $hosts[]['status']								Status of the host (optional).
578	 * @param array  $hosts[]['interfaces']							An array of host interfaces data to be replaced.
579	 * @param int    $hosts[]['interfaces']['type']					Interface type.
580	 * @param int    $hosts[]['interfaces']['main']					Is this the default interface to use.
581	 * @param string $hosts[]['interfaces']['ip']					Interface IP (optional).
582	 * @param int    $hosts[]['interfaces']['port']					Interface port (optional).
583	 * @param int    $hosts[]['interfaces']['useip']				Interface shoud use IP (optional).
584	 * @param string $hosts[]['interfaces']['dns']					Interface shoud use DNS (optional).
585	 * @param int    $hosts[]['interfaces']['bulk']					Use bulk requests for interface (optional).
586	 * @param int    $hosts[]['proxy_hostid']						ID of the proxy that is used to monitor the host (optional).
587	 * @param int    $hosts[]['ipmi_authtype']						IPMI authentication type (optional).
588	 * @param int    $hosts[]['ipmi_privilege']						IPMI privilege (optional).
589	 * @param string $hosts[]['ipmi_username']						IPMI username (optional).
590	 * @param string $hosts[]['ipmi_password']						IPMI password (optional).
591	 * @param array  $hosts[]['inventory']							An array of host inventory data (optional).
592	 * @param array  $hosts[]['macros']								An array of host macros (optional).
593	 * @param string $hosts[]['macros'][]['macro']					Host macro (required if "macros" is set).
594	 * @param array  $hosts[]['templates']							An array of template objects with IDs that will be linked to host (optional).
595	 * @param string $hosts[]['templates'][]['templateid']			Template ID (required if "templates" is set).
596	 * @param array  $hosts[]['templates_clear']					Templates to unlink and clear from the host (optional).
597	 * @param string $hosts[]['templates_clear'][]['templateid']	Template ID (required if "templates" is set).
598	 * @param string $hosts[]['tls_connect']						Connections to host (optional).
599	 * @param string $hosts[]['tls_accept']							Connections from host (optional).
600	 * @param string $hosts[]['tls_psk_identity']					PSK identity (required if "PSK" type is set).
601	 * @param string $hosts[]['tls_psk']							PSK (required if "PSK" type is set).
602	 * @param string $hosts[]['tls_issuer']							Certificate issuer (optional).
603	 * @param string $hosts[]['tls_subject']						Certificate subject (optional).
604	 *
605	 * @return array
606	 */
607	public function update($hosts) {
608		$hosts = zbx_toArray($hosts);
609		$hostids = zbx_objectValues($hosts, 'hostid');
610
611		$db_hosts = $this->get([
612			'output' => ['hostid', 'host', 'flags', 'tls_connect', 'tls_accept', 'tls_issuer', 'tls_subject',
613				'tls_psk_identity', 'tls_psk'
614			],
615			'hostids' => $hostids,
616			'editable' => true,
617			'preservekeys' => true
618		]);
619
620		$hosts = $this->validateUpdate($hosts, $db_hosts);
621
622		$inventories = [];
623		foreach ($hosts as &$host) {
624			// If visible name is not given or empty it should be set to host name.
625			if (array_key_exists('host', $host) && (!array_key_exists('name', $host) || !trim($host['name']))) {
626				$host['name'] = $host['host'];
627			}
628
629			// Fetch fields required to update host inventory.
630			if (array_key_exists('inventory', $host)) {
631				$inventory = $host['inventory'];
632				$inventory['hostid'] = $host['hostid'];
633
634				$inventories[] = $inventory;
635			}
636		}
637		unset($host);
638
639		$inventories = $this->extendObjects('host_inventory', $inventories, ['inventory_mode']);
640		$inventories = zbx_toHash($inventories, 'hostid');
641
642		$macros = [];
643		foreach ($hosts as &$host) {
644			if (isset($host['macros'])) {
645				$macros[$host['hostid']] = $host['macros'];
646
647				unset($host['macros']);
648			}
649		}
650		unset($host);
651
652		if ($macros) {
653			API::UserMacro()->replaceMacros($macros);
654		}
655
656		$hosts = $this->extendObjectsByKey($hosts, $db_hosts, 'hostid', ['tls_connect', 'tls_accept', 'tls_issuer',
657			'tls_subject', 'tls_psk_identity', 'tls_psk'
658		]);
659
660		foreach ($hosts as $host) {
661			// Extend host inventory with the required data.
662			if (array_key_exists('inventory', $host) && $host['inventory']) {
663				// If inventory mode is HOST_INVENTORY_DISABLED, database record is not created.
664				if (array_key_exists('inventory_mode', $inventories[$host['hostid']])
665						&& ($inventories[$host['hostid']]['inventory_mode'] == HOST_INVENTORY_MANUAL
666							|| $inventories[$host['hostid']]['inventory_mode'] == HOST_INVENTORY_AUTOMATIC)) {
667					$host['inventory'] = $inventories[$host['hostid']];
668				}
669			}
670
671			$data = $host;
672			$data['hosts'] = ['hostid' => $host['hostid']];
673			$result = $this->massUpdate($data);
674
675			if (!$result) {
676				self::exception(ZBX_API_ERROR_INTERNAL, _('Host update failed.'));
677			}
678		}
679
680		return ['hostids' => $hostids];
681	}
682
683	/**
684	 * Additionally allows to create new interfaces on hosts.
685	 *
686	 * Checks write permissions for hosts.
687	 *
688	 * Additional supported $data parameters are:
689	 * - interfaces - an array of interfaces to create on the hosts
690	 * - templates  - an array of templates to link to the hosts, overrides the CHostGeneral::massAdd()
691	 *                'templates' parameter
692	 *
693	 * @param array $data
694	 *
695	 * @return array
696	 */
697	public function massAdd(array $data) {
698		$hosts = isset($data['hosts']) ? zbx_toArray($data['hosts']) : [];
699		$hostIds = zbx_objectValues($hosts, 'hostid');
700
701		// check permissions
702		if (!$this->isWritable($hostIds)) {
703			self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.'));
704		}
705
706		// add new interfaces
707		if (!empty($data['interfaces'])) {
708			API::HostInterface()->massAdd([
709				'hosts' => $data['hosts'],
710				'interfaces' => zbx_toArray($data['interfaces'])
711			]);
712		}
713
714		// rename the "templates" parameter to the common "templates_link"
715		if (isset($data['templates'])) {
716			$data['templates_link'] = $data['templates'];
717			unset($data['templates']);
718		}
719
720		$data['templates'] = [];
721
722		return parent::massAdd($data);
723	}
724
725	/**
726	 * Mass update hosts.
727	 *
728	 * @param array  $hosts								multidimensional array with Hosts data
729	 * @param array  $hosts['hosts']					Array of Host objects to update
730	 * @param string $hosts['fields']['host']			Host name.
731	 * @param array  $hosts['fields']['groupids']		HostGroup IDs add Host to.
732	 * @param int    $hosts['fields']['port']			Port. OPTIONAL
733	 * @param int    $hosts['fields']['status']			Host Status. OPTIONAL
734	 * @param int    $hosts['fields']['useip']			Use IP. OPTIONAL
735	 * @param string $hosts['fields']['dns']			DNS. OPTIONAL
736	 * @param string $hosts['fields']['ip']				IP. OPTIONAL
737	 * @param int    $hosts['fields']['bulk']			bulk. OPTIONAL
738	 * @param int    $hosts['fields']['proxy_hostid']	Proxy Host ID. OPTIONAL
739	 * @param int    $hosts['fields']['ipmi_authtype']	IPMI authentication type. OPTIONAL
740	 * @param int    $hosts['fields']['ipmi_privilege']	IPMI privilege. OPTIONAL
741	 * @param string $hosts['fields']['ipmi_username']	IPMI username. OPTIONAL
742	 * @param string $hosts['fields']['ipmi_password']	IPMI password. OPTIONAL
743	 *
744	 * @return boolean
745	 */
746	public function massUpdate($data) {
747		if (!array_key_exists('hosts', $data) || !is_array($data['hosts'])) {
748			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Field "%1$s" is mandatory.', 'hosts'));
749		}
750
751		$hosts = zbx_toArray($data['hosts']);
752		$inputHostIds = zbx_objectValues($hosts, 'hostid');
753		$hostids = array_unique($inputHostIds);
754
755		sort($hostids);
756
757		$db_hosts = $this->get([
758			'output' => ['hostid', 'host'],
759			'hostids' => $hostids,
760			'editable' => true,
761			'preservekeys' => true
762		]);
763
764		foreach ($hosts as $host) {
765			if (!array_key_exists($host['hostid'], $db_hosts)) {
766				self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.'));
767			}
768		}
769
770		// Check inventory mode value.
771		if (array_key_exists('inventory_mode', $data)) {
772			$valid_inventory_modes = [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC];
773			$inventory_mode = new CLimitedSetValidator([
774				'values' => $valid_inventory_modes,
775				'messageInvalid' => _s('Incorrect value for field "%1$s": %2$s.', 'inventory_mode',
776					_s('value must be one of %1$s', implode(', ', $valid_inventory_modes)))
777			]);
778			$this->checkValidator($data['inventory_mode'], $inventory_mode);
779		}
780
781		// Check connection fields only for massupdate action.
782		if (array_key_exists('tls_connect', $data) || array_key_exists('tls_accept', $data)
783				|| array_key_exists('tls_psk_identity', $data) || array_key_exists('tls_psk', $data)
784				|| array_key_exists('tls_issuer', $data) || array_key_exists('tls_subject', $data)) {
785			if (!array_key_exists('tls_connect', $data) || !array_key_exists('tls_accept', $data)) {
786				self::exception(ZBX_API_ERROR_PERMISSIONS, _(
787					'Cannot update host encryption settings. Connection settings for both directions should be specified.'
788				));
789			}
790
791			// Clean PSK fields.
792			if ($data['tls_connect'] != HOST_ENCRYPTION_PSK && !($data['tls_accept'] & HOST_ENCRYPTION_PSK)) {
793				$data['tls_psk_identity'] = '';
794				$data['tls_psk'] = '';
795			}
796
797			// Clean certificate fields.
798			if ($data['tls_connect'] != HOST_ENCRYPTION_CERTIFICATE
799					&& !($data['tls_accept'] & HOST_ENCRYPTION_CERTIFICATE)) {
800				$data['tls_issuer'] = '';
801				$data['tls_subject'] = '';
802			}
803		}
804
805		$this->validateEncryption([$data]);
806
807		if (array_key_exists('groups', $data) && !$data['groups'] && $db_hosts) {
808			$host = reset($db_hosts);
809
810			self::exception(ZBX_API_ERROR_PARAMETERS,
811				_s('Host "%1$s" cannot be without host group.', $host['host'])
812			);
813		}
814
815		/*
816		 * Update hosts properties
817		 */
818		if (isset($data['name'])) {
819			if (count($hosts) > 1) {
820				self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot mass update visible host name.'));
821			}
822		}
823
824		if (isset($data['host'])) {
825			if (!preg_match('/^'.ZBX_PREG_HOST_FORMAT.'$/', $data['host'])) {
826				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect characters used for host name "%s".', $data['host']));
827			}
828
829			if (count($hosts) > 1) {
830				self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot mass update host name.'));
831			}
832
833			$curHost = reset($hosts);
834
835			$sameHostnameHost = $this->get([
836				'output' => ['hostid'],
837				'filter' => ['host' => $data['host']],
838				'nopermissions' => true,
839				'limit' => 1
840			]);
841			$sameHostnameHost = reset($sameHostnameHost);
842			if ($sameHostnameHost && (bccomp($sameHostnameHost['hostid'], $curHost['hostid']) != 0)) {
843				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Host "%1$s" already exists.', $data['host']));
844			}
845
846			// can't add host with the same name as existing template
847			$sameHostnameTemplate = API::Template()->get([
848				'output' => ['templateid'],
849				'filter' => ['host' => $data['host']],
850				'nopermissions' => true,
851				'limit' => 1
852			]);
853			if ($sameHostnameTemplate) {
854				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Template "%1$s" already exists.', $data['host']));
855			}
856		}
857
858		if (isset($data['groups'])) {
859			$updateGroups = $data['groups'];
860		}
861
862		if (isset($data['interfaces'])) {
863			$updateInterfaces = $data['interfaces'];
864		}
865
866		if (array_key_exists('templates_clear', $data)) {
867			$updateTemplatesClear = zbx_toArray($data['templates_clear']);
868		}
869
870		if (isset($data['templates'])) {
871			$updateTemplates = $data['templates'];
872		}
873
874		if (isset($data['macros'])) {
875			$updateMacros = $data['macros'];
876		}
877
878		// second check is necessary, because import incorrectly inputs unset 'inventory' as empty string rather than null
879		if (isset($data['inventory']) && $data['inventory']) {
880			if (isset($data['inventory_mode']) && $data['inventory_mode'] == HOST_INVENTORY_DISABLED) {
881				self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot set inventory fields for disabled inventory.'));
882			}
883
884			$updateInventory = $data['inventory'];
885			$updateInventory['inventory_mode'] = null;
886		}
887
888		if (isset($data['inventory_mode'])) {
889			if (!isset($updateInventory)) {
890				$updateInventory = [];
891			}
892			$updateInventory['inventory_mode'] = $data['inventory_mode'];
893		}
894
895		if (isset($data['status'])) {
896			$updateStatus = $data['status'];
897		}
898
899		unset($data['hosts'], $data['groups'], $data['interfaces'], $data['templates_clear'], $data['templates'],
900			$data['macros'], $data['inventory'], $data['inventory_mode'], $data['status']);
901
902		if (!zbx_empty($data)) {
903			DB::update('hosts', [
904				'values' => $data,
905				'where' => ['hostid' => $hostids]
906			]);
907		}
908
909		if (isset($updateStatus)) {
910			updateHostStatus($hostids, $updateStatus);
911		}
912
913		/*
914		 * Update template linkage
915		 */
916		if (isset($updateTemplatesClear)) {
917			$templateIdsClear = zbx_objectValues($updateTemplatesClear, 'templateid');
918
919			if ($updateTemplatesClear) {
920				$this->massRemove(['hostids' => $hostids, 'templateids_clear' => $templateIdsClear]);
921			}
922		}
923		else {
924			$templateIdsClear = [];
925		}
926
927		// unlink templates
928		if (isset($updateTemplates)) {
929			$hostTemplates = API::Template()->get([
930				'hostids' => $hostids,
931				'output' => ['templateid'],
932				'preservekeys' => true
933			]);
934
935			$hostTemplateids = array_keys($hostTemplates);
936			$newTemplateids = zbx_objectValues($updateTemplates, 'templateid');
937
938			$templatesToDel = array_diff($hostTemplateids, $newTemplateids);
939			$templatesToDel = array_diff($templatesToDel, $templateIdsClear);
940
941			if ($templatesToDel) {
942				$result = $this->massRemove([
943					'hostids' => $hostids,
944					'templateids' => $templatesToDel
945				]);
946				if (!$result) {
947					self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot unlink template'));
948				}
949			}
950		}
951
952		/*
953		 * update interfaces
954		 */
955		if (isset($updateInterfaces)) {
956			foreach($hostids as $hostid) {
957				API::HostInterface()->replaceHostInterfaces([
958					'hostid' => $hostid,
959					'interfaces' => $updateInterfaces
960				]);
961			}
962		}
963
964		// link new templates
965		if (isset($updateTemplates)) {
966			$result = $this->massAdd([
967				'hosts' => $hosts,
968				'templates' => $updateTemplates
969			]);
970
971			if (!$result) {
972				self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot link template'));
973			}
974		}
975
976		// macros
977		if (isset($updateMacros)) {
978			DB::delete('hostmacro', ['hostid' => $hostids]);
979
980			$this->massAdd([
981				'hosts' => $hosts,
982				'macros' => $updateMacros
983			]);
984		}
985
986		/*
987		 * Inventory
988		 */
989		if (isset($updateInventory)) {
990			// disabling inventory
991			if ($updateInventory['inventory_mode'] == HOST_INVENTORY_DISABLED) {
992				$sql = 'DELETE FROM host_inventory WHERE '.dbConditionInt('hostid', $hostids);
993				if (!DBexecute($sql)) {
994					self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot delete inventory.'));
995				}
996			}
997			// changing inventory mode or setting inventory fields
998			else {
999				$existingInventoriesDb = DBfetchArrayAssoc(DBselect(
1000					'SELECT hostid,inventory_mode'.
1001					' FROM host_inventory'.
1002					' WHERE '.dbConditionInt('hostid', $hostids)
1003				), 'hostid');
1004
1005				// check existing host inventory data
1006				$automaticHostIds = [];
1007				if ($updateInventory['inventory_mode'] === null) {
1008					foreach ($hostids as $hostid) {
1009						// if inventory is disabled for one of the updated hosts, throw an exception
1010						if (!isset($existingInventoriesDb[$hostid])) {
1011							$host = get_host_by_hostid($hostid);
1012							self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1013								'Inventory disabled for host "%1$s".', $host['host']
1014							));
1015						}
1016						// if inventory mode is set to automatic, save its ID for later usage
1017						elseif ($existingInventoriesDb[$hostid]['inventory_mode'] == HOST_INVENTORY_AUTOMATIC) {
1018							$automaticHostIds[] = $hostid;
1019						}
1020					}
1021				}
1022
1023				$inventoriesToSave = [];
1024				foreach ($hostids as $hostid) {
1025					$hostInventory = $updateInventory;
1026					$hostInventory['hostid'] = $hostid;
1027
1028					// if no 'inventory_mode' has been passed, set inventory 'inventory_mode' from DB
1029					if ($updateInventory['inventory_mode'] === null) {
1030						$hostInventory['inventory_mode'] = $existingInventoriesDb[$hostid]['inventory_mode'];
1031					}
1032
1033					$inventoriesToSave[$hostid] = $hostInventory;
1034				}
1035
1036				// when updating automatic inventory, ignore fields that have items linked to them
1037				if ($updateInventory['inventory_mode'] == HOST_INVENTORY_AUTOMATIC
1038						|| ($updateInventory['inventory_mode'] === null && $automaticHostIds)) {
1039
1040					$itemsToInventories = API::item()->get([
1041						'output' => ['inventory_link', 'hostid'],
1042						'hostids' => $automaticHostIds ? $automaticHostIds : $hostids,
1043						'nopermissions' => true
1044					]);
1045
1046					$inventoryFields = getHostInventories();
1047					foreach ($itemsToInventories as $hinv) {
1048						// 0 means 'no link'
1049						if ($hinv['inventory_link'] != 0) {
1050							$inventoryName = $inventoryFields[$hinv['inventory_link']]['db_field'];
1051							unset($inventoriesToSave[$hinv['hostid']][$inventoryName]);
1052						}
1053					}
1054				}
1055
1056				// save inventory data
1057				foreach ($inventoriesToSave as $inventory) {
1058					$hostid = $inventory['hostid'];
1059					if (isset($existingInventoriesDb[$hostid])) {
1060						DB::update('host_inventory', [
1061							'values' => $inventory,
1062							'where' => ['hostid' => $hostid]
1063						]);
1064					}
1065					else {
1066						DB::insert('host_inventory', [$inventory], false);
1067					}
1068				}
1069			}
1070		}
1071
1072		/*
1073		 * Update host and host group linkage. This procedure should be done the last because user can unlink
1074		 * him self from a group with write permissions leaving only read premissions. Thus other procedures, like
1075		 * host-template linkage, inventory update, macros update, must be done before this.
1076		 */
1077		if (isset($updateGroups)) {
1078			$updateGroups = zbx_toArray($updateGroups);
1079
1080			$hostGroups = API::HostGroup()->get([
1081				'output' => ['groupid'],
1082				'hostids' => $hostids
1083			]);
1084			$hostGroupIds = zbx_objectValues($hostGroups, 'groupid');
1085			$newGroupIds = zbx_objectValues($updateGroups, 'groupid');
1086
1087			$groupsToAdd = array_diff($newGroupIds, $hostGroupIds);
1088			if ($groupsToAdd) {
1089				$this->massAdd([
1090					'hosts' => $hosts,
1091					'groups' => zbx_toObject($groupsToAdd, 'groupid')
1092				]);
1093			}
1094
1095			$groupIdsToDelete = array_diff($hostGroupIds, $newGroupIds);
1096			if ($groupIdsToDelete) {
1097				$this->massRemove([
1098					'hostids' => $hostids,
1099					'groupids' => $groupIdsToDelete
1100				]);
1101			}
1102		}
1103
1104		return ['hostids' => $inputHostIds];
1105	}
1106
1107	/**
1108	 * Additionally allows to remove interfaces from hosts.
1109	 *
1110	 * Checks write permissions for hosts.
1111	 *
1112	 * Additional supported $data parameters are:
1113	 * - interfaces  - an array of interfaces to delete from the hosts
1114	 *
1115	 * @param array $data
1116	 *
1117	 * @return array
1118	 */
1119	public function massRemove(array $data) {
1120		$hostids = zbx_toArray($data['hostids']);
1121
1122		$this->checkPermissions($hostids);
1123
1124		if (isset($data['interfaces'])) {
1125			$options = [
1126				'hostids' => $hostids,
1127				'interfaces' => zbx_toArray($data['interfaces'])
1128			];
1129			API::HostInterface()->massRemove($options);
1130		}
1131
1132		// rename the "templates" parameter to the common "templates_link"
1133		if (isset($data['templateids'])) {
1134			$data['templateids_link'] = $data['templateids'];
1135			unset($data['templateids']);
1136		}
1137
1138		$data['templateids'] = [];
1139
1140		return parent::massRemove($data);
1141	}
1142
1143	/**
1144	 * Validates the input parameters for the delete() method.
1145	 *
1146	 * @throws APIException if the input is invalid
1147	 *
1148	 * @param array $hostIds
1149	 * @param bool 	$nopermissions
1150	 *
1151	 * @return void
1152	 */
1153	protected function validateDelete(array $hostIds, $nopermissions = false) {
1154		if (!$hostIds) {
1155			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
1156		}
1157
1158		if (!$nopermissions) {
1159			$this->checkPermissions($hostIds);
1160		}
1161	}
1162
1163	/**
1164	 * Delete Host.
1165	 *
1166	 * @param array	$hostIds
1167	 * @param bool	$nopermissions
1168	 *
1169	 * @return array
1170	 */
1171	public function delete(array $hostIds, $nopermissions = false) {
1172		$this->validateDelete($hostIds, $nopermissions);
1173
1174		// delete the discovery rules first
1175		$delRules = API::DiscoveryRule()->get([
1176			'output' => ['itemid'],
1177			'hostids' => $hostIds,
1178			'nopermissions' => true,
1179			'preservekeys' => true
1180		]);
1181		if ($delRules) {
1182			API::DiscoveryRule()->delete(array_keys($delRules), true);
1183		}
1184
1185		// delete the items
1186		$delItems = API::Item()->get([
1187			'templateids' => $hostIds,
1188			'output' => ['itemid'],
1189			'nopermissions' => true,
1190			'preservekeys' => true
1191		]);
1192		if ($delItems) {
1193			API::Item()->delete(array_keys($delItems), true);
1194		}
1195
1196		// delete web tests
1197		$delHttptests = [];
1198		$dbHttptests = get_httptests_by_hostid($hostIds);
1199		while ($dbHttptest = DBfetch($dbHttptests)) {
1200			$delHttptests[$dbHttptest['httptestid']] = $dbHttptest['httptestid'];
1201		}
1202		if (!empty($delHttptests)) {
1203			API::HttpTest()->delete($delHttptests, true);
1204		}
1205
1206
1207		// delete screen items
1208		DB::delete('screens_items', [
1209			'resourceid' => $hostIds,
1210			'resourcetype' => SCREEN_RESOURCE_HOST_TRIGGERS
1211		]);
1212
1213		// delete host from maps
1214		if (!empty($hostIds)) {
1215			DB::delete('sysmaps_elements', [
1216				'elementtype' => SYSMAP_ELEMENT_TYPE_HOST,
1217				'elementid' => $hostIds
1218			]);
1219		}
1220
1221		// disable actions
1222		// actions from conditions
1223		$actionids = [];
1224		$sql = 'SELECT DISTINCT actionid'.
1225				' FROM conditions'.
1226				' WHERE conditiontype='.CONDITION_TYPE_HOST.
1227				' AND '.dbConditionString('value', $hostIds);
1228		$dbActions = DBselect($sql);
1229		while ($dbAction = DBfetch($dbActions)) {
1230			$actionids[$dbAction['actionid']] = $dbAction['actionid'];
1231		}
1232
1233		// actions from operations
1234		$sql = 'SELECT DISTINCT o.actionid'.
1235				' FROM operations o, opcommand_hst oh'.
1236				' WHERE o.operationid=oh.operationid'.
1237				' AND '.dbConditionInt('oh.hostid', $hostIds);
1238		$dbActions = DBselect($sql);
1239		while ($dbAction = DBfetch($dbActions)) {
1240			$actionids[$dbAction['actionid']] = $dbAction['actionid'];
1241		}
1242
1243		if (!empty($actionids)) {
1244			$update = [];
1245			$update[] = [
1246				'values' => ['status' => ACTION_STATUS_DISABLED],
1247				'where' => ['actionid' => $actionids]
1248			];
1249			DB::update('actions', $update);
1250		}
1251
1252		// delete action conditions
1253		DB::delete('conditions', [
1254			'conditiontype' => CONDITION_TYPE_HOST,
1255			'value' => $hostIds
1256		]);
1257
1258		// delete action operation commands
1259		$operationids = [];
1260		$sql = 'SELECT DISTINCT oh.operationid'.
1261				' FROM opcommand_hst oh'.
1262				' WHERE '.dbConditionInt('oh.hostid', $hostIds);
1263		$dbOperations = DBselect($sql);
1264		while ($dbOperation = DBfetch($dbOperations)) {
1265			$operationids[$dbOperation['operationid']] = $dbOperation['operationid'];
1266		}
1267
1268		DB::delete('opcommand_hst', [
1269			'hostid' => $hostIds,
1270		]);
1271
1272		// delete empty operations
1273		$delOperationids = [];
1274		$sql = 'SELECT DISTINCT o.operationid'.
1275				' FROM operations o'.
1276				' WHERE '.dbConditionInt('o.operationid', $operationids).
1277				' AND NOT EXISTS(SELECT oh.opcommand_hstid FROM opcommand_hst oh WHERE oh.operationid=o.operationid)';
1278		$dbOperations = DBselect($sql);
1279		while ($dbOperation = DBfetch($dbOperations)) {
1280			$delOperationids[$dbOperation['operationid']] = $dbOperation['operationid'];
1281		}
1282
1283		DB::delete('operations', [
1284			'operationid' => $delOperationids,
1285		]);
1286
1287		$hosts = API::Host()->get([
1288			'output' => [
1289				'hostid',
1290				'name'
1291			],
1292			'hostids' => $hostIds,
1293			'nopermissions' => true
1294		]);
1295
1296		// delete host inventory
1297		DB::delete('host_inventory', ['hostid' => $hostIds]);
1298
1299		// delete host applications
1300		DB::delete('applications', ['hostid' => $hostIds]);
1301
1302		// delete host
1303		DB::delete('hosts', ['hostid' => $hostIds]);
1304
1305		// TODO: remove info from API
1306		foreach ($hosts as $host) {
1307			info(_s('Deleted: Host "%1$s".', $host['name']));
1308			add_audit_ext(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_HOST, $host['hostid'], $host['name'], 'hosts', NULL, NULL);
1309		}
1310
1311		// remove Monitoring > Latest data toggle profile values related to given hosts
1312		DB::delete('profiles', ['idx' => 'web.latest.toggle_other', 'idx2' => $hostIds]);
1313
1314		return ['hostids' => $hostIds];
1315	}
1316
1317	/**
1318	 * Check if user has read permissions for host.
1319	 *
1320	 * @param array $ids
1321	 *
1322	 * @return bool
1323	 */
1324	public function isReadable(array $ids) {
1325		if (!is_array($ids)) {
1326			return false;
1327		}
1328		if (empty($ids)) {
1329			return true;
1330		}
1331
1332		$ids = array_unique($ids);
1333
1334		$count = $this->get([
1335			'hostids' => $ids,
1336			'templated_hosts' => true,
1337			'countOutput' => true
1338		]);
1339
1340		return (count($ids) == $count);
1341	}
1342
1343	/**
1344	 * Check if user has write permissions for host.
1345	 *
1346	 * @param array $ids
1347	 *
1348	 * @return bool
1349	 */
1350	public function isWritable(array $ids) {
1351		if (!is_array($ids)) {
1352			return false;
1353		}
1354		if (empty($ids)) {
1355			return true;
1356		}
1357
1358		$ids = array_unique($ids);
1359
1360		$count = $this->get([
1361			'hostids' => $ids,
1362			'editable' => true,
1363			'templated_hosts' => true,
1364			'countOutput' => true
1365		]);
1366
1367		return (count($ids) == $count);
1368	}
1369
1370	protected function addRelatedObjects(array $options, array $result) {
1371		$result = parent::addRelatedObjects($options, $result);
1372
1373		$hostids = array_keys($result);
1374
1375		// adding inventories
1376		if ($options['selectInventory'] !== null) {
1377			$relationMap = $this->createRelationMap($result, 'hostid', 'hostid');
1378			$inventory = API::getApiService()->select('host_inventory', [
1379				'output' => $options['selectInventory'],
1380				'filter' => ['hostid' => $hostids]
1381			]);
1382			$result = $relationMap->mapOne($result, zbx_toHash($inventory, 'hostid'), 'inventory');
1383		}
1384
1385		// adding hostinterfaces
1386		if ($options['selectInterfaces'] !== null) {
1387			if ($options['selectInterfaces'] != API_OUTPUT_COUNT) {
1388				$interfaces = API::HostInterface()->get([
1389					'output' => $this->outputExtend($options['selectInterfaces'], ['hostid', 'interfaceid']),
1390					'hostids' => $hostids,
1391					'nopermissions' => true,
1392					'preservekeys' => true
1393				]);
1394
1395				// we need to order interfaces for proper linkage and viewing
1396				order_result($interfaces, 'interfaceid', ZBX_SORT_UP);
1397
1398				$relationMap = $this->createRelationMap($interfaces, 'hostid', 'interfaceid');
1399
1400				$interfaces = $this->unsetExtraFields($interfaces, ['hostid', 'interfaceid'], $options['selectInterfaces']);
1401				$result = $relationMap->mapMany($result, $interfaces, 'interfaces', $options['limitSelects']);
1402			}
1403			else {
1404				$interfaces = API::HostInterface()->get([
1405					'hostids' => $hostids,
1406					'nopermissions' => true,
1407					'countOutput' => true,
1408					'groupCount' => true
1409				]);
1410
1411				$interfaces = zbx_toHash($interfaces, 'hostid');
1412				foreach ($result as $hostid => $host) {
1413					$result[$hostid]['interfaces'] = isset($interfaces[$hostid]) ? $interfaces[$hostid]['rowscount'] : 0;
1414				}
1415			}
1416		}
1417
1418		// adding screens
1419		if ($options['selectScreens'] !== null) {
1420			if ($options['selectScreens'] != API_OUTPUT_COUNT) {
1421				$screens = API::TemplateScreen()->get([
1422					'output' => $this->outputExtend($options['selectScreens'], ['hostid']),
1423					'hostids' => $hostids,
1424					'nopermissions' => true
1425				]);
1426				if (!is_null($options['limitSelects'])) {
1427					order_result($screens, 'name');
1428				}
1429
1430				// inherited screens do not have a unique screenid, so we're building a map using array keys
1431				$relationMap = new CRelationMap();
1432				foreach ($screens as $key => $screen) {
1433					$relationMap->addRelation($screen['hostid'], $key);
1434				}
1435
1436				$screens = $this->unsetExtraFields($screens, ['hostid'], $options['selectScreens']);
1437				$result = $relationMap->mapMany($result, $screens, 'screens', $options['limitSelects']);
1438			}
1439			else {
1440				$screens = API::TemplateScreen()->get([
1441					'hostids' => $hostids,
1442					'nopermissions' => true,
1443					'countOutput' => true,
1444					'groupCount' => true
1445				]);
1446				$screens = zbx_toHash($screens, 'hostid');
1447
1448				foreach ($result as $hostid => $host) {
1449					$result[$hostid]['screens'] = isset($screens[$hostid]) ? $screens[$hostid]['rowscount'] : 0;
1450				}
1451			}
1452		}
1453
1454		// adding discovery rule
1455		if ($options['selectDiscoveryRule'] !== null && $options['selectDiscoveryRule'] != API_OUTPUT_COUNT) {
1456			// discovered items
1457			$discoveryRules = DBFetchArray(DBselect(
1458				'SELECT hd.hostid,hd2.parent_itemid'.
1459					' FROM host_discovery hd,host_discovery hd2'.
1460					' WHERE '.dbConditionInt('hd.hostid', $hostids).
1461					' AND hd.parent_hostid=hd2.hostid'
1462			));
1463			$relationMap = $this->createRelationMap($discoveryRules, 'hostid', 'parent_itemid');
1464
1465			$discoveryRules = API::DiscoveryRule()->get([
1466				'output' => $options['selectDiscoveryRule'],
1467				'itemids' => $relationMap->getRelatedIds(),
1468				'preservekeys' => true
1469			]);
1470			$result = $relationMap->mapOne($result, $discoveryRules, 'discoveryRule');
1471		}
1472
1473		// adding host discovery
1474		if ($options['selectHostDiscovery'] !== null) {
1475			$hostDiscoveries = API::getApiService()->select('host_discovery', [
1476				'output' => $this->outputExtend($options['selectHostDiscovery'], ['hostid']),
1477				'filter' => ['hostid' => $hostids],
1478				'preservekeys' => true
1479			]);
1480			$relationMap = $this->createRelationMap($hostDiscoveries, 'hostid', 'hostid');
1481
1482			$hostDiscoveries = $this->unsetExtraFields($hostDiscoveries, ['hostid'],
1483				$options['selectHostDiscovery']
1484			);
1485			$result = $relationMap->mapOne($result, $hostDiscoveries, 'hostDiscovery');
1486		}
1487
1488		return $result;
1489	}
1490
1491	/**
1492	 * Checks if all of the given hosts are available for writing.
1493	 *
1494	 * @throws APIException     if a host is not writable or does not exist
1495	 *
1496	 * @param array $hostIds
1497	 */
1498	protected function checkPermissions(array $hostIds) {
1499		if (!$this->isWritable($hostIds)) {
1500			self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
1501		}
1502	}
1503
1504	/**
1505	 * Validate connections from/to host and PSK fields.
1506	 *
1507	 * @param array  $hosts
1508	 * @param string $hosts[]['hostid']                    (optional if $db_hosts is null)
1509	 * @param int    $hosts[]['tls_connect']               (optionsl)
1510	 * @param int    $hosts[]['tls_accept']                (optional)
1511	 * @param string $hosts[]['tls_psk_identity']          (optional)
1512	 * @param string $hosts[]['tls_psk']                   (optional)
1513	 * @param string $hosts[]['tls_issuer']                (optional)
1514	 * @param string $hosts[]['tls_subject']               (optional)
1515	 * @param array  $db_hosts                             (optional)
1516	 * @param int    $hosts[<hostid>]['tls_connect']
1517	 * @param int    $hosts[<hostid>]['tls_accept']
1518	 * @param string $hosts[<hostid>]['tls_psk_identity']
1519	 * @param string $hosts[<hostid>]['tls_psk']
1520	 * @param string $hosts[<hostid>]['tls_issuer']
1521	 * @param string $hosts[<hostid>]['tls_subject']
1522	 *
1523	 * @throws APIException if incorrect encryption options.
1524	 */
1525	protected function validateEncryption(array $hosts, array $db_hosts = null) {
1526		$available_connect_types = [HOST_ENCRYPTION_NONE, HOST_ENCRYPTION_PSK, HOST_ENCRYPTION_CERTIFICATE];
1527		$min_accept_type = HOST_ENCRYPTION_NONE;
1528		$max_accept_type = HOST_ENCRYPTION_NONE | HOST_ENCRYPTION_PSK | HOST_ENCRYPTION_CERTIFICATE;
1529
1530		foreach ($hosts as $host) {
1531			foreach (['tls_connect', 'tls_accept'] as $field_name) {
1532				$$field_name = array_key_exists($field_name, $host)
1533					? $host[$field_name]
1534					: ($db_hosts !== null ? $db_hosts[$host['hostid']][$field_name] : HOST_ENCRYPTION_NONE);
1535			}
1536
1537			if (!in_array($tls_connect, $available_connect_types)) {
1538				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_connect',
1539					_s('unexpected value "%1$s"', $tls_connect)
1540				));
1541			}
1542
1543			if ($tls_accept < $min_accept_type || $tls_accept > $max_accept_type) {
1544				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_accept',
1545					_s('unexpected value "%1$s"', $tls_accept)
1546				));
1547			}
1548
1549			foreach (['tls_psk_identity', 'tls_psk', 'tls_issuer', 'tls_subject'] as $field_name) {
1550				$$field_name = array_key_exists($field_name, $host)
1551					? $host[$field_name]
1552					: ($db_hosts !== null ? $db_hosts[$host['hostid']][$field_name] : '');
1553			}
1554
1555			// PSK validation.
1556			if ($tls_connect == HOST_ENCRYPTION_PSK || ($tls_accept & HOST_ENCRYPTION_PSK)) {
1557				if ($tls_psk_identity === '') {
1558					self::exception(ZBX_API_ERROR_PARAMETERS,
1559						_s('Incorrect value for field "%1$s": %2$s.', 'tls_psk_identity', _('cannot be empty'))
1560					);
1561				}
1562
1563				if ($tls_psk === '') {
1564					self::exception(ZBX_API_ERROR_PARAMETERS,
1565						_s('Incorrect value for field "%1$s": %2$s.', 'tls_psk', _('cannot be empty'))
1566					);
1567				}
1568
1569				if (!preg_match('/^([0-9a-f]{2})+$/i', $tls_psk)) {
1570					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_psk',
1571						_('an even number of hexadecimal characters is expected')
1572					));
1573				}
1574
1575				if (strlen($tls_psk) < PSK_MIN_LEN) {
1576					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_psk',
1577						_s('minimum length is %1$s characters', PSK_MIN_LEN)
1578					));
1579				}
1580			}
1581			else {
1582				if ($tls_psk_identity !== '') {
1583					self::exception(ZBX_API_ERROR_PARAMETERS,
1584						_s('Incorrect value for field "%1$s": %2$s.', 'tls_psk_identity', _('should be empty'))
1585					);
1586				}
1587
1588				if ($tls_psk !== '') {
1589					self::exception(ZBX_API_ERROR_PARAMETERS,
1590						_s('Incorrect value for field "%1$s": %2$s.', 'tls_psk', _('should be empty'))
1591					);
1592				}
1593			}
1594
1595			// Certificate validation.
1596			if ($tls_connect != HOST_ENCRYPTION_CERTIFICATE && !($tls_accept & HOST_ENCRYPTION_CERTIFICATE)) {
1597				if ($tls_issuer !== '') {
1598					self::exception(ZBX_API_ERROR_PARAMETERS,
1599						_s('Incorrect value for field "%1$s": %2$s.', 'tls_issuer', _('should be empty'))
1600					);
1601				}
1602
1603				if ($tls_subject !== '') {
1604					self::exception(ZBX_API_ERROR_PARAMETERS,
1605						_s('Incorrect value for field "%1$s": %2$s.', 'tls_subject', _('should be empty'))
1606					);
1607				}
1608			}
1609		}
1610	}
1611
1612	/**
1613	 * Validates the input parameters for the create() method.
1614	 *
1615	 * @param array $hosts		hosts data array
1616	 *
1617	 * @throws APIException if the input is invalid.
1618	 */
1619	protected function validateCreate(array $hosts) {
1620		$host_db_fields = ['host' => null];
1621
1622		$groupids = [];
1623
1624		foreach ($hosts as &$host) {
1625			// Validate mandatory fields.
1626			if (!check_db_fields($host_db_fields, $host)) {
1627				self::exception(ZBX_API_ERROR_PARAMETERS,
1628					_s('Wrong fields for host "%1$s".', array_key_exists('host', $host) ? $host['host'] : '')
1629				);
1630			}
1631
1632			// Validate "host" field.
1633			if (!preg_match('/^'.ZBX_PREG_HOST_FORMAT.'$/', $host['host'])) {
1634				self::exception(ZBX_API_ERROR_PARAMETERS,
1635					_s('Incorrect characters used for host name "%s".', $host['host'])
1636				);
1637			}
1638
1639			// If visible name is not given or empty it should be set to host name. Required for duplicate checks.
1640			if (!array_key_exists('name', $host) || !trim($host['name'])) {
1641				$host['name'] = $host['host'];
1642			}
1643
1644			// Validate "groups" field.
1645			if (!array_key_exists('groups', $host) || !is_array($host['groups']) || !$host['groups']) {
1646				self::exception(ZBX_API_ERROR_PARAMETERS,
1647					_s('Host "%1$s" cannot be without host group.', $host['host'])
1648				);
1649			}
1650
1651			$groupids = array_merge($groupids, zbx_objectValues($host['groups'], 'groupid'));
1652		}
1653		unset($host);
1654
1655		// Check for duplicate "host" and "name" fields.
1656		$duplicate = CArrayHelper::findDuplicate($hosts, 'host');
1657		if ($duplicate) {
1658			self::exception(ZBX_API_ERROR_PARAMETERS,
1659				_s('Duplicate host. Host with the same host name "%s" already exists in data.', $duplicate['host'])
1660			);
1661		}
1662
1663		$duplicate = CArrayHelper::findDuplicate($hosts, 'name');
1664		if ($duplicate) {
1665			self::exception(ZBX_API_ERROR_PARAMETERS,
1666				_s('Duplicate host. Host with the same visible name "%s" already exists in data.', $duplicate['name'])
1667			);
1668		}
1669
1670		// Validate permissions to host groups.
1671		if ($groupids) {
1672			$db_groups = API::HostGroup()->get([
1673				'output' => ['groupid'],
1674				'groupids' => $groupids,
1675				'editable' => true,
1676				'preservekeys' => true
1677			]);
1678		}
1679
1680		foreach ($hosts as $host) {
1681			foreach ($host['groups'] as $group) {
1682				if (!array_key_exists($group['groupid'], $db_groups)) {
1683					self::exception(ZBX_API_ERROR_PERMISSIONS,
1684						_('No permissions to referred object or it does not exist!')
1685					);
1686				}
1687			}
1688		}
1689
1690		$inventory_fields = zbx_objectValues(getHostInventories(), 'db_field');
1691
1692		$valid_inventory_modes = [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC];
1693		$inventory_mode = new CLimitedSetValidator([
1694			'values' => $valid_inventory_modes,
1695			'messageInvalid' => _s('Incorrect value for field "%1$s": %2$s.', 'inventory_mode',
1696				_s('value must be one of %1$s', implode(', ', $valid_inventory_modes)))
1697		]);
1698
1699		$status_validator = new CLimitedSetValidator([
1700			'values' => [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED],
1701			'messageInvalid' => _('Incorrect status for host "%1$s".')
1702		]);
1703
1704		$host_names = [];
1705
1706		foreach ($hosts as $host) {
1707			if (!array_key_exists('interfaces', $host) || !is_array($host['interfaces']) || !$host['interfaces']) {
1708				self::exception(ZBX_API_ERROR_PARAMETERS, _s('No interfaces for host "%s".', $host['host']));
1709			}
1710
1711			if (array_key_exists('status', $host)) {
1712				$status_validator->setObjectName($host['host']);
1713				$this->checkValidator($host['status'], $status_validator);
1714			}
1715
1716			if (array_key_exists('inventory_mode', $host)) {
1717				$inventory_mode->setObjectName($host['host']);
1718				$this->checkValidator($host['inventory_mode'], $inventory_mode);
1719			}
1720
1721			if (array_key_exists('inventory', $host) && $host['inventory']) {
1722				if (array_key_exists('inventory_mode', $host) && $host['inventory_mode'] == HOST_INVENTORY_DISABLED) {
1723					self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot set inventory fields for disabled inventory.'));
1724				}
1725
1726				$fields = array_keys($host['inventory']);
1727				foreach ($fields as $field) {
1728					if (!in_array($field, $inventory_fields)) {
1729						self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect inventory field "%s".', $field));
1730					}
1731				}
1732			}
1733
1734			// Collect technical and visible names to check if they exist in hosts and templates.
1735			$host_names['host'][$host['host']] = true;
1736			$host_names['name'][$host['name']] = true;
1737		}
1738
1739		$filter = [
1740			'host' => array_keys($host_names['host']),
1741			'name' => array_keys($host_names['name'])
1742		];
1743
1744		$hosts_exists = $this->get([
1745			'output' => ['host', 'name'],
1746			'filter' => $filter,
1747			'searchByAny' => true,
1748			'nopermissions' => true
1749		]);
1750
1751		foreach ($hosts_exists as $host_exists) {
1752			if (array_key_exists($host_exists['host'], $host_names['host'])) {
1753				self::exception(ZBX_API_ERROR_PARAMETERS,
1754					_s('Host with the same name "%s" already exists.', $host_exists['host'])
1755				);
1756			}
1757
1758			if (array_key_exists($host_exists['name'], $host_names['name'])) {
1759				self::exception(ZBX_API_ERROR_PARAMETERS,
1760					_s('Host with the same visible name "%s" already exists.', $host_exists['name'])
1761				);
1762			}
1763		}
1764
1765		$templates_exists = API::Template()->get([
1766			'output' => ['host', 'name'],
1767			'filter' => $filter,
1768			'searchByAny' => true,
1769			'nopermissions' => true
1770		]);
1771
1772		foreach ($templates_exists as $template_exists) {
1773			if (array_key_exists($template_exists['host'], $host_names['host'])) {
1774				self::exception(ZBX_API_ERROR_PARAMETERS,
1775					_s('Template with the same name "%s" already exists.', $template_exists['host'])
1776				);
1777			}
1778
1779			if (array_key_exists($template_exists['name'], $host_names['name'])) {
1780				self::exception(ZBX_API_ERROR_PARAMETERS,
1781					_s('Template with the same visible name "%s" already exists.', $template_exists['name'])
1782				);
1783			}
1784		}
1785
1786		$this->validateEncryption($hosts);
1787	}
1788
1789	/**
1790	 * Validates the input parameters for the update() method.
1791	 *
1792	 * @param array $hosts			hosts data array
1793	 * @param array $db_hosts		db hosts data array
1794	 *
1795	 * @throws APIException if the input is invalid.
1796	 */
1797	protected function validateUpdate(array $hosts, array $db_hosts) {
1798		$host_db_fields = ['hostid' => null];
1799
1800		foreach ($hosts as $host) {
1801			// Validate mandatory fields.
1802			if (!check_db_fields($host_db_fields, $host)) {
1803				self::exception(ZBX_API_ERROR_PARAMETERS,
1804					_s('Wrong fields for host "%1$s".', array_key_exists('host', $host) ? $host['host'] : '')
1805				);
1806			}
1807
1808			// Validate host permissions.
1809			if (!array_key_exists($host['hostid'], $db_hosts)) {
1810				self::exception(ZBX_API_ERROR_PARAMETERS, _(
1811					'No permissions to referred object or it does not exist!'
1812				));
1813			}
1814
1815			// Validate "groups" field.
1816			if (array_key_exists('groups', $host) && (!is_array($host['groups']) || !$host['groups'])) {
1817				self::exception(ZBX_API_ERROR_PARAMETERS,
1818					_s('Host "%1$s" cannot be without host group.', $db_hosts[$host['hostid']]['host'])
1819				);
1820			}
1821
1822			// Permissions to host groups is validated in massUpdate().
1823		}
1824
1825		$inventory_fields = zbx_objectValues(getHostInventories(), 'db_field');
1826
1827		$valid_inventory_modes = [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC];
1828		$inventory_mode = new CLimitedSetValidator([
1829			'values' => $valid_inventory_modes,
1830			'messageInvalid' => _s('Incorrect value for field "%1$s": %2$s.', 'inventory_mode',
1831				_s('value must be one of %1$s', implode(', ', $valid_inventory_modes)))
1832		]);
1833
1834		$status_validator = new CLimitedSetValidator([
1835			'values' => [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED],
1836			'messageInvalid' => _('Incorrect status for host "%1$s".')
1837		]);
1838
1839		$update_discovered_validator = new CUpdateDiscoveredValidator([
1840			'allowed' => ['hostid', 'status', 'inventory', 'description'],
1841			'messageAllowedField' => _('Cannot update "%2$s" for a discovered host "%1$s".')
1842		]);
1843
1844		$host_names = [];
1845
1846		foreach ($hosts as &$host) {
1847			$db_host = $db_hosts[$host['hostid']];
1848			$host_name = array_key_exists('host', $host) ? $host['host'] : $db_host['host'];
1849
1850			if (array_key_exists('status', $host)) {
1851				$status_validator->setObjectName($host_name);
1852				$this->checkValidator($host['status'], $status_validator);
1853			}
1854
1855			if (array_key_exists('inventory_mode', $host)) {
1856				$inventory_mode->setObjectName($host_name);
1857				$this->checkValidator($host['inventory_mode'], $inventory_mode);
1858			}
1859
1860			if (array_key_exists('inventory', $host) && $host['inventory']) {
1861				if (array_key_exists('inventory_mode', $host) && $host['inventory_mode'] == HOST_INVENTORY_DISABLED) {
1862					self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot set inventory fields for disabled inventory.'));
1863				}
1864
1865				$fields = array_keys($host['inventory']);
1866				foreach ($fields as $field) {
1867					if (!in_array($field, $inventory_fields)) {
1868						self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect inventory field "%s".', $field));
1869					}
1870				}
1871			}
1872
1873			// cannot update certain fields for discovered hosts
1874			$update_discovered_validator->setObjectName($host_name);
1875			$this->checkPartialValidator($host, $update_discovered_validator, $db_host);
1876
1877			if (array_key_exists('interfaces', $host)) {
1878				if (!is_array($host['interfaces']) || !$host['interfaces']) {
1879					self::exception(ZBX_API_ERROR_PARAMETERS, _s('No interfaces for host "%s".', $host['host']));
1880				}
1881			}
1882
1883			if (array_key_exists('host', $host)) {
1884				if (!preg_match('/^'.ZBX_PREG_HOST_FORMAT.'$/', $host['host'])) {
1885					self::exception(ZBX_API_ERROR_PARAMETERS,
1886						_s('Incorrect characters used for host name "%s".', $host['host'])
1887					);
1888				}
1889
1890				if (array_key_exists('host', $host_names) && array_key_exists($host['host'], $host_names['host'])) {
1891					self::exception(ZBX_API_ERROR_PARAMETERS,
1892						_s('Duplicate host. Host with the same host name "%s" already exists in data.', $host['host'])
1893					);
1894				}
1895
1896				$host_names['host'][$host['host']] = $host['hostid'];
1897			}
1898
1899			if (array_key_exists('name', $host)) {
1900				// if visible name is empty replace it with host name
1901				if (zbx_empty(trim($host['name']))) {
1902					if (!array_key_exists('host', $host)) {
1903						self::exception(ZBX_API_ERROR_PARAMETERS,
1904							_s('Visible name cannot be empty if host name is missing.')
1905						);
1906					}
1907					$host['name'] = $host['host'];
1908				}
1909
1910				if (array_key_exists('name', $host_names) && array_key_exists($host['name'], $host_names['name'])) {
1911					self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1912						'Duplicate host. Host with the same visible name "%s" already exists in data.', $host['name'])
1913					);
1914				}
1915				$host_names['name'][$host['name']] = $host['hostid'];
1916			}
1917
1918			if (array_key_exists('tls_connect', $host) || array_key_exists('tls_accept', $host)) {
1919				$tls_connect = array_key_exists('tls_connect', $host) ? $host['tls_connect'] : $db_host['tls_connect'];
1920				$tls_accept = array_key_exists('tls_accept', $host) ? $host['tls_accept'] : $db_host['tls_accept'];
1921
1922				// Clean PSK fields.
1923				if ($tls_connect != HOST_ENCRYPTION_PSK && !($tls_accept & HOST_ENCRYPTION_PSK)) {
1924					if (!array_key_exists('tls_psk_identity', $host)) {
1925						$host['tls_psk_identity'] = '';
1926					}
1927					if (!array_key_exists('tls_psk', $host)) {
1928						$host['tls_psk'] = '';
1929					}
1930				}
1931
1932				// Clean certificate fields.
1933				if ($tls_connect != HOST_ENCRYPTION_CERTIFICATE && !($tls_accept & HOST_ENCRYPTION_CERTIFICATE)) {
1934					if (!array_key_exists('tls_issuer', $host)) {
1935						$host['tls_issuer'] = '';
1936					}
1937					if (!array_key_exists('tls_subject', $host)) {
1938						$host['tls_subject'] = '';
1939					}
1940				}
1941			}
1942		}
1943		unset($host);
1944
1945		if (array_key_exists('host', $host_names) || array_key_exists('name', $host_names)) {
1946			$filter = [];
1947
1948			if (array_key_exists('host', $host_names)) {
1949				$filter['host'] = array_keys($host_names['host']);
1950			}
1951
1952			if (array_key_exists('name', $host_names)) {
1953				$filter['name'] = array_keys($host_names['name']);
1954			}
1955
1956			$hosts_exists = $this->get([
1957				'output' => ['hostid', 'host', 'name'],
1958				'filter' => $filter,
1959				'searchByAny' => true,
1960				'nopermissions' => true,
1961				'preservekeys' => true
1962			]);
1963
1964			foreach ($hosts_exists as $host_exists) {
1965				if (array_key_exists('host', $host_names) && array_key_exists($host_exists['host'], $host_names['host'])
1966						&& bccomp($host_exists['hostid'], $host_names['host'][$host_exists['host']]) != 0) {
1967					self::exception(ZBX_API_ERROR_PARAMETERS,
1968						_s('Host with the same name "%s" already exists.', $host_exists['host'])
1969					);
1970				}
1971
1972				if (array_key_exists('name', $host_names) && array_key_exists($host_exists['name'], $host_names['name'])
1973						&& bccomp($host_exists['hostid'], $host_names['name'][$host_exists['name']]) != 0) {
1974					self::exception(ZBX_API_ERROR_PARAMETERS,
1975						_s('Host with the same visible name "%s" already exists.', $host_exists['name'])
1976					);
1977				}
1978			}
1979
1980			$templates_exists = API::Template()->get([
1981				'output' => ['hostid', 'host', 'name'],
1982				'filter' => $filter,
1983				'searchByAny' => true,
1984				'nopermissions' => true,
1985				'preservekeys' => true
1986			]);
1987
1988			foreach ($templates_exists as $template_exists) {
1989				if (array_key_exists('host', $host_names)
1990						&& array_key_exists($template_exists['host'], $host_names['host'])
1991						&& bccomp($template_exists['templateid'], $host_names['host'][$template_exists['host']]) != 0) {
1992					self::exception(ZBX_API_ERROR_PARAMETERS,
1993						_s('Template with the same name "%s" already exists.', $template_exists['host'])
1994					);
1995				}
1996
1997				if (array_key_exists('name', $host_names)
1998						&& array_key_exists($template_exists['name'], $host_names['name'])
1999						&& bccomp($template_exists['templateid'], $host_names['name'][$template_exists['name']]) != 0) {
2000					self::exception(ZBX_API_ERROR_PARAMETERS,
2001						_s('Template with the same visible name "%s" already exists.', $template_exists['name'])
2002					);
2003				}
2004			}
2005		}
2006
2007		$this->validateEncryption($hosts, $db_hosts);
2008
2009		return $hosts;
2010	}
2011}
2012