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 host interfaces.
24 *
25 * @package API
26 */
27class CHostInterface extends CApiService {
28
29	protected $tableName = 'interface';
30	protected $tableAlias = 'hi';
31	protected $sortColumns = ['interfaceid', 'dns', 'ip'];
32
33	/**
34	 * Get interface data.
35	 *
36	 * @param array  $options
37	 * @param array  $options['hostids']		Interface IDs
38	 * @param bool   $options['editable']		only with read-write permission. Ignored for SuperAdmins
39	 * @param bool   $options['selectHosts']	select Interface hosts
40	 * @param bool   $options['selectItems']	select Items
41	 * @param int    $options['count']			count Interfaces, returned column name is rowscount
42	 * @param string $options['pattern']		search hosts by pattern in Interface name
43	 * @param int    $options['limit']			limit selection
44	 * @param string $options['sortfield']		field to sort by
45	 * @param string $options['sortorder']		sort order
46	 *
47	 * @return array|boolean Interface data as array or false if error
48	 */
49	public function get(array $options = []) {
50		$result = [];
51
52		$sqlParts = [
53			'select'	=> ['interface' => 'hi.interfaceid'],
54			'from'		=> ['interface' => 'interface hi'],
55			'where'		=> [],
56			'group'		=> [],
57			'order'		=> [],
58			'limit'		=> null
59		];
60
61		$defOptions = [
62			'groupids'					=> null,
63			'hostids'					=> null,
64			'interfaceids'				=> null,
65			'itemids'					=> null,
66			'triggerids'				=> null,
67			'editable'					=> false,
68			'nopermissions'				=> null,
69			// filter
70			'filter'					=> null,
71			'search'					=> null,
72			'searchByAny'				=> null,
73			'startSearch'				=> null,
74			'excludeSearch'				=> null,
75			'searchWildcardsEnabled'	=> null,
76			// output
77			'output'					=> API_OUTPUT_EXTEND,
78			'selectHosts'				=> null,
79			'selectItems'				=> null,
80			'countOutput'				=> null,
81			'groupCount'				=> null,
82			'preservekeys'				=> null,
83			'sortfield'					=> '',
84			'sortorder'					=> '',
85			'limit'						=> null,
86			'limitSelects'				=> null
87		];
88		$options = zbx_array_merge($defOptions, $options);
89
90		// editable + PERMISSION CHECK
91		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) {
92			$permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ;
93			$userGroups = getUserGroupsByUserId(self::$userData['userid']);
94
95			$sqlParts['where'][] = 'EXISTS ('.
96				'SELECT NULL'.
97				' FROM hosts_groups hgg'.
98					' JOIN rights r'.
99						' ON r.id=hgg.groupid'.
100							' AND '.dbConditionInt('r.groupid', $userGroups).
101				' WHERE hi.hostid=hgg.hostid'.
102				' GROUP BY hgg.hostid'.
103				' HAVING MIN(r.permission)>'.PERM_DENY.
104					' AND MAX(r.permission)>='.zbx_dbstr($permission).
105				')';
106		}
107
108		// interfaceids
109		if (!is_null($options['interfaceids'])) {
110			zbx_value2array($options['interfaceids']);
111			$sqlParts['where']['interfaceid'] = dbConditionInt('hi.interfaceid', $options['interfaceids']);
112		}
113
114		// hostids
115		if (!is_null($options['hostids'])) {
116			zbx_value2array($options['hostids']);
117			$sqlParts['where']['hostid'] = dbConditionInt('hi.hostid', $options['hostids']);
118		}
119
120		// itemids
121		if (!is_null($options['itemids'])) {
122			zbx_value2array($options['itemids']);
123
124			$sqlParts['from']['items'] = 'items i';
125			$sqlParts['where'][] = dbConditionInt('i.itemid', $options['itemids']);
126			$sqlParts['where']['hi'] = 'hi.interfaceid=i.interfaceid';
127		}
128
129		// triggerids
130		if (!is_null($options['triggerids'])) {
131			zbx_value2array($options['triggerids']);
132
133			$sqlParts['from']['functions'] = 'functions f';
134			$sqlParts['from']['items'] = 'items i';
135			$sqlParts['where'][] = dbConditionInt('f.triggerid', $options['triggerids']);
136			$sqlParts['where']['hi'] = 'hi.hostid=i.hostid';
137			$sqlParts['where']['fi'] = 'f.itemid=i.itemid';
138		}
139
140		// search
141		if (is_array($options['search'])) {
142			zbx_db_search('interface hi', $options, $sqlParts);
143		}
144
145		// filter
146		if (is_array($options['filter'])) {
147			$this->dbFilter('interface hi', $options, $sqlParts);
148		}
149
150		// limit
151		if (zbx_ctype_digit($options['limit']) && $options['limit']) {
152			$sqlParts['limit'] = $options['limit'];
153		}
154
155		$sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
156		$sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
157		$res = DBselect($this->createSelectQueryFromParts($sqlParts), $sqlParts['limit']);
158		while ($interface = DBfetch($res)) {
159			if (!is_null($options['countOutput'])) {
160				if (!is_null($options['groupCount'])) {
161					$result[] = $interface;
162				}
163				else {
164					$result = $interface['rowscount'];
165				}
166			}
167			else {
168				$result[$interface['interfaceid']] = $interface;
169			}
170		}
171
172		if (!is_null($options['countOutput'])) {
173			return $result;
174		}
175
176		if ($result) {
177			$result = $this->addRelatedObjects($options, $result);
178			$result = $this->unsetExtraFields($result, ['hostid'], $options['output']);
179		}
180
181		// removing keys (hash -> array)
182		if (is_null($options['preservekeys'])) {
183			$result = zbx_cleanHashes($result);
184		}
185
186		return $result;
187	}
188
189	/**
190	 * Check interfaces input.
191	 *
192	 * @param array  $interfaces
193	 * @param string $method
194	 */
195	public function checkInput(array &$interfaces, $method) {
196		$update = ($method == 'update');
197
198		// permissions
199		if ($update) {
200			$interfaceDBfields = ['interfaceid' => null];
201			$dbInterfaces = $this->get([
202				'output' => API_OUTPUT_EXTEND,
203				'interfaceids' => zbx_objectValues($interfaces, 'interfaceid'),
204				'editable' => true,
205				'preservekeys' => true
206			]);
207		}
208		else {
209			$interfaceDBfields = [
210				'hostid' => null,
211				'ip' => null,
212				'dns' => null,
213				'useip' => null,
214				'port' => null,
215				'main' => null
216			];
217		}
218
219		$dbHosts = API::Host()->get([
220			'output' => ['host'],
221			'hostids' => zbx_objectValues($interfaces, 'hostid'),
222			'editable' => true,
223			'preservekeys' => true
224		]);
225
226		$dbProxies = API::Proxy()->get([
227			'output' => ['host'],
228			'proxyids' => zbx_objectValues($interfaces, 'hostid'),
229			'editable' => true,
230			'preservekeys' => true
231		]);
232
233		$check_have_items = [];
234		foreach ($interfaces as &$interface) {
235			if (!check_db_fields($interfaceDBfields, $interface)) {
236				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
237			}
238
239			if ($update) {
240				if (!isset($dbInterfaces[$interface['interfaceid']])) {
241					self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!'));
242				}
243
244				$dbInterface = $dbInterfaces[$interface['interfaceid']];
245				if (isset($interface['hostid']) && bccomp($dbInterface['hostid'], $interface['hostid']) != 0) {
246					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Cannot switch host for interface.'));
247				}
248
249				if (array_key_exists('type', $interface) && $interface['type'] != $dbInterface['type']) {
250					$check_have_items[] = $interface['interfaceid'];
251				}
252
253				$interface['hostid'] = $dbInterface['hostid'];
254
255				// we check all fields on "updated" interface
256				$updInterface = $interface;
257				$interface = zbx_array_merge($dbInterface, $interface);
258			}
259			else {
260				if (!isset($dbHosts[$interface['hostid']]) && !isset($dbProxies[$interface['hostid']])) {
261					self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!'));
262				}
263
264				if (isset($dbProxies[$interface['hostid']])) {
265					$interface['type'] = INTERFACE_TYPE_UNKNOWN;
266				}
267				elseif (!isset($interface['type'])) {
268					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to method.'));
269				}
270			}
271
272			if (zbx_empty($interface['ip']) && zbx_empty($interface['dns'])) {
273				self::exception(ZBX_API_ERROR_PARAMETERS, _('IP and DNS cannot be empty for host interface.'));
274			}
275
276			if ($interface['useip'] == INTERFACE_USE_IP && zbx_empty($interface['ip'])) {
277				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Interface with DNS "%1$s" cannot have empty IP address.', $interface['dns']));
278			}
279
280			if ($interface['useip'] == INTERFACE_USE_DNS && zbx_empty($interface['dns'])) {
281				if ($dbHosts && !empty($dbHosts[$interface['hostid']]['host'])) {
282					self::exception(ZBX_API_ERROR_PARAMETERS,
283						_s('Interface with IP "%1$s" cannot have empty DNS name while having "Use DNS" property on "%2$s".',
284							$interface['ip'],
285							$dbHosts[$interface['hostid']]['host']
286					));
287				}
288				elseif ($dbProxies && !empty($dbProxies[$interface['hostid']]['host'])) {
289					self::exception(ZBX_API_ERROR_PARAMETERS,
290						_s('Interface with IP "%1$s" cannot have empty DNS name while having "Use DNS" property on "%2$s".',
291							$interface['ip'],
292							$dbProxies[$interface['hostid']]['host']
293					));
294				}
295				else {
296					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Interface with IP "%1$s" cannot have empty DNS name.', $interface['ip']));
297				}
298			}
299
300			if (isset($interface['dns'])) {
301				$this->checkDns($interface);
302			}
303			if (isset($interface['ip'])) {
304				$this->checkIp($interface);
305			}
306			if (isset($interface['port']) || $method == 'create') {
307				$this->checkPort($interface);
308			}
309
310			$this->checkBulk($interface);
311
312			if ($update) {
313				$interface = $updInterface;
314			}
315		}
316		unset($interface);
317
318		// check if any of the affected hosts are discovered
319		if ($update) {
320			$interfaces = $this->extendObjects('interface', $interfaces, ['hostid']);
321
322			if ($check_have_items) {
323				$this->checkIfInterfaceHasItems($check_have_items);
324			}
325		}
326		$this->checkValidator(zbx_objectValues($interfaces, 'hostid'), new CHostNormalValidator([
327			'message' => _('Cannot update interface for discovered host "%1$s".')
328		]));
329	}
330
331	/**
332	 * Add interfaces.
333	 *
334	 * @param array $interfaces multidimensional array with Interfaces data
335	 *
336	 * @return array
337	 */
338	public function create(array $interfaces) {
339		$interfaces = zbx_toArray($interfaces);
340
341		$this->checkInput($interfaces, __FUNCTION__);
342		$this->checkMainInterfacesOnCreate($interfaces);
343
344		$interfaceIds = DB::insert('interface', $interfaces);
345
346		return ['interfaceids' => $interfaceIds];
347	}
348
349	/**
350	 * Update interfaces.
351	 *
352	 * @param array $interfaces multidimensional array with Interfaces data
353	 *
354	 * @return array
355	 */
356	public function update(array $interfaces) {
357		$interfaces = zbx_toArray($interfaces);
358
359		$this->checkInput($interfaces, __FUNCTION__);
360		$this->checkMainInterfacesOnUpdate($interfaces);
361
362		$data = [];
363		foreach ($interfaces as $interface) {
364			$data[] = [
365				'values' => $interface,
366				'where' => ['interfaceid' => $interface['interfaceid']]
367			];
368		}
369		DB::update('interface', $data);
370
371		return ['interfaceids' => zbx_objectValues($interfaces, 'interfaceid')];
372	}
373
374	protected function clearValues(array $interface) {
375		if (isset($interface['port']) && $interface['port'] != '') {
376			$interface['port'] = ltrim($interface['port'], '0');
377
378			if ($interface['port'] == '') {
379				$interface['port'] = 0;
380			}
381		}
382
383		return $interface;
384	}
385
386	/**
387	 * Delete interfaces.
388	 * Interface cannot be deleted if it's main interface and exists other interface of same type on same host.
389	 * Interface cannot be deleted if it is used in items.
390	 *
391	 * @param array $interfaceids
392	 *
393	 * @return array
394	 */
395	public function delete(array $interfaceids) {
396		if (empty($interfaceids)) {
397			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
398		}
399
400		$dbInterfaces = $this->get([
401			'output' => API_OUTPUT_EXTEND,
402			'interfaceids' => $interfaceids,
403			'editable' => true,
404			'preservekeys' => true
405		]);
406		foreach ($interfaceids as $interfaceId) {
407			if (!isset($dbInterfaces[$interfaceId])) {
408				self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!'));
409			}
410		}
411
412		$this->checkMainInterfacesOnDelete($interfaceids);
413
414		DB::delete('interface', ['interfaceid' => $interfaceids]);
415
416		return ['interfaceids' => $interfaceids];
417	}
418
419	public function massAdd(array $data) {
420		$interfaces = zbx_toArray($data['interfaces']);
421		$hosts = zbx_toArray($data['hosts']);
422
423		$insertData = [];
424		foreach ($interfaces as $interface) {
425			foreach ($hosts as $host) {
426				$newInterface = $interface;
427				$newInterface['hostid'] = $host['hostid'];
428
429				$insertData[] = $newInterface;
430			}
431		}
432
433		$interfaceIds = $this->create($insertData);
434
435		return ['interfaceids' => $interfaceIds];
436	}
437
438	protected function validateMassRemove(array $data) {
439		// check permissions
440		$this->checkHostPermissions($data['hostids']);
441
442		// check interfaces
443		foreach ($data['interfaces'] as $interface) {
444			if (!isset($interface['dns']) || !isset($interface['ip']) || !isset($interface['port'])) {
445				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
446			}
447
448			$this->checkDns($interface);
449			$this->checkIp($interface);
450			$this->checkPort($interface);
451			$this->checkBulk($interface);
452
453			// check main interfaces
454			$interfacesToRemove = API::getApiService()->select($this->tableName(), [
455				'output' => ['interfaceid'],
456				'filter' => [
457					'hostid' => $data['hostids'],
458					'ip' => $interface['ip'],
459					'dns' => $interface['dns'],
460					'port' => $interface['port'],
461					'bulk' => $interface['bulk']
462				]
463			]);
464			if ($interfacesToRemove) {
465				$this->checkMainInterfacesOnDelete(zbx_objectValues($interfacesToRemove, 'interfaceid'));
466			}
467		}
468	}
469
470	/**
471	 * Remove hosts from interfaces.
472	 *
473	 * @param array $data
474	 * @param array $data['interfaceids']
475	 * @param array $data['hostids']
476	 * @param array $data['templateids']
477	 *
478	 * @return array
479	 */
480	public function massRemove(array $data) {
481		$data['interfaces'] = zbx_toArray($data['interfaces']);
482		$data['hostids'] = zbx_toArray($data['hostids']);
483
484		$this->validateMassRemove($data);
485
486		$interfaceIds = [];
487		foreach ($data['interfaces'] as $interface) {
488			$interfaces = $this->get([
489				'output' => ['interfaceid'],
490				'filter' => [
491					'hostid' => $data['hostids'],
492					'ip' => $interface['ip'],
493					'dns' => $interface['dns'],
494					'port' => $interface['port'],
495					'bulk' => $interface['bulk']
496				],
497				'editable' => true,
498				'preservekeys' => true
499			]);
500
501			if ($interfaces) {
502				$interfaceIds = array_merge($interfaceIds, array_keys($interfaces));
503			}
504		}
505
506		if ($interfaceIds) {
507			$interfaceIds = array_keys(array_flip($interfaceIds));
508			DB::delete('interface', ['interfaceid' => $interfaceIds]);
509		}
510
511		return ['interfaceids' => $interfaceIds];
512	}
513
514	/**
515	 * Replace existing interfaces with input interfaces.
516	 *
517	 * @param array $host
518	 */
519	public function replaceHostInterfaces(array $host) {
520		if (isset($host['interfaces']) && !is_null($host['interfaces'])) {
521			$host['interfaces'] = zbx_toArray($host['interfaces']);
522
523			$this->checkHostInterfaces($host['interfaces'], $host['hostid']);
524
525			$interfacesToDelete = API::HostInterface()->get([
526				'output' => [],
527				'hostids' => $host['hostid'],
528				'preservekeys' => true,
529				'nopermissions' => true
530			]);
531
532			$interfacesToAdd = [];
533			$interfacesToUpdate = [];
534
535			foreach ($host['interfaces'] as $interface) {
536				$interface['hostid'] = $host['hostid'];
537
538				if (!isset($interface['interfaceid'])) {
539					$interfacesToAdd[] = $interface;
540				}
541				elseif (isset($interfacesToDelete[$interface['interfaceid']])) {
542					$interfacesToUpdate[] = $interface;
543					unset($interfacesToDelete[$interface['interfaceid']]);
544				}
545			}
546
547			if ($interfacesToUpdate) {
548				API::HostInterface()->checkInput($interfacesToUpdate, 'update');
549
550				$data = [];
551				foreach ($interfacesToUpdate as $interface) {
552					$data[] = [
553						'values' => $interface,
554						'where' => ['interfaceid' => $interface['interfaceid']]
555					];
556				}
557				DB::update('interface', $data);
558			}
559
560			if ($interfacesToAdd) {
561				$this->checkInput($interfacesToAdd, 'create');
562				$interfaceids = DB::insert('interface', $interfacesToAdd);
563
564				foreach ($host['interfaces'] as &$interface) {
565					if (!array_key_exists('interfaceid', $interface)) {
566						$interface['interfaceid'] = array_shift($interfaceids);
567					}
568				}
569				unset($interface);
570			}
571
572			if ($interfacesToDelete) {
573				$this->delete(zbx_objectValues($interfacesToDelete, 'interfaceid'));
574			}
575
576			return ['interfaceids' => zbx_objectValues($host['interfaces'], 'interfaceid')];
577		}
578
579		return ['interfaceids' => []];
580	}
581
582	/**
583	 * Validates the "dns" field.
584	 *
585	 * @throws APIException if the field is invalid.
586	 *
587	 * @param array $interface
588	 * @param string $interface['dns']
589	 */
590	protected function checkDns(array $interface) {
591		if ($interface['dns'] === '') {
592			return;
593		}
594
595		$user_macro_parser = new CUserMacroParser();
596
597		if (!preg_match('/^'.ZBX_PREG_DNS_FORMAT.'$/', $interface['dns'])
598				&& $user_macro_parser->parse($interface['dns']) != CParser::PARSE_SUCCESS) {
599			self::exception(ZBX_API_ERROR_PARAMETERS,
600				_s('Incorrect interface DNS parameter "%s" provided.', $interface['dns'])
601			);
602		}
603	}
604
605	/**
606	 * Validates the "ip" field.
607	 *
608	 * @throws APIException if the field is invalid.
609	 *
610	 * @param array $interface
611	 * @param string $interface['ip']
612	 */
613	protected function checkIp(array $interface) {
614		if ($interface['ip'] === '') {
615			return;
616		}
617
618		$user_macro_parser = new CUserMacroParser();
619
620		if (preg_match('/^'.ZBX_PREG_MACRO_NAME_FORMAT.'$/', $interface['ip'])
621				|| $user_macro_parser->parse($interface['ip']) == CParser::PARSE_SUCCESS) {
622			return;
623		}
624
625		$ipValidator = new CIPValidator();
626		if (!$ipValidator->validate($interface['ip'])) {
627			self::exception(ZBX_API_ERROR_PARAMETERS, $ipValidator->getError());
628		}
629	}
630
631	/**
632	 * Validates the "port" field.
633	 *
634	 * @throws APIException if the field is empty or invalid.
635	 *
636	 * @param array $interface
637	 */
638	protected function checkPort(array $interface) {
639		if (!isset($interface['port']) || zbx_empty($interface['port'])) {
640			self::exception(ZBX_API_ERROR_PARAMETERS, _('Port cannot be empty for host interface.'));
641		}
642		elseif (!validatePortNumberOrMacro($interface['port'])) {
643			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect interface port "%s" provided.', $interface['port']));
644		}
645	}
646
647	/**
648	 * Checks if the current user has access to the given hosts. Assumes the "hostid" field is valid.
649	 *
650	 * @throws APIException if the user doesn't have write permissions for the given hosts
651	 *
652	 * @param array $hostIds	an array of host IDs
653	 */
654	protected function checkHostPermissions(array $hostIds) {
655		if (!API::Host()->isWritable($hostIds)) {
656			self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
657		}
658	}
659
660	/**
661	 * Validates interface "bulk" field.
662	 * For SNMP interfaces bulk value should be either 0 (disabled) or 1 (enabled).
663	 * For other non-SNMP interfaces bulk value should be 1 (default).
664	 *
665	 * @throws APIException if bulk field is incorrect.
666	 *
667	 * @param array $interface
668	 */
669	protected function checkBulk(array $interface) {
670		if ($interface['type'] !== null && (($interface['type'] != INTERFACE_TYPE_SNMP && isset($interface['bulk'])
671				&& $interface['bulk'] != SNMP_BULK_ENABLED)
672				|| ($interface['type'] == INTERFACE_TYPE_SNMP && isset($interface['bulk'])
673					&& (zbx_empty($interface['bulk'])
674						|| ($interface['bulk'] != SNMP_BULK_DISABLED && $interface['bulk'] != SNMP_BULK_ENABLED))))) {
675			self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect bulk value for interface.'));
676		}
677	}
678
679	private function checkHostInterfaces(array $interfaces, $hostid) {
680		$interfacesWithMissingData = [];
681
682		foreach ($interfaces as $interface) {
683			if (!isset($interface['type'], $interface['main'])) {
684				$interfacesWithMissingData[] = $interface['interfaceid'];
685			}
686		}
687
688		if ($interfacesWithMissingData) {
689			$dbInterfaces = API::HostInterface()->get([
690				'interfaceids' => $interfacesWithMissingData,
691				'output' => ['main', 'type'],
692				'preservekeys' => true,
693				'nopermissions' => true
694			]);
695		}
696
697		foreach ($interfaces as $id => $interface) {
698			if (isset($interface['interfaceid']) && isset($dbInterfaces[$interface['interfaceid']])) {
699				$interfaces[$id] = array_merge($interface, $dbInterfaces[$interface['interfaceid']]);
700			}
701			$interfaces[$id]['hostid'] = $hostid;
702		}
703
704		$this->checkMainInterfaces($interfaces);
705	}
706
707	private function checkMainInterfacesOnCreate(array $interfaces) {
708		$hostIds = [];
709		foreach ($interfaces as $interface) {
710			$hostIds[$interface['hostid']] = $interface['hostid'];
711		}
712
713		$dbInterfaces = API::HostInterface()->get([
714			'hostids' => $hostIds,
715			'output' => ['hostid', 'main', 'type'],
716			'preservekeys' => true,
717			'nopermissions' => true
718		]);
719		$interfaces = array_merge($dbInterfaces, $interfaces);
720
721		$this->checkMainInterfaces($interfaces);
722	}
723
724	private function checkMainInterfacesOnUpdate(array $interfaces) {
725		$interfaceidsWithoutHostIds = [];
726
727		// gather all hostids where interfaces should be checked
728		foreach ($interfaces as $interface) {
729			if (isset($interface ['type']) || isset($interface['main'])) {
730				if (isset($interface['hostid'])) {
731					$hostids[$interface['hostid']] = $interface['hostid'];
732				}
733				else {
734					$interfaceidsWithoutHostIds[] = $interface['interfaceid'];
735				}
736			}
737		}
738
739		// gather missing host ids
740		$hostIds = [];
741		if ($interfaceidsWithoutHostIds) {
742			$dbResult = DBselect('SELECT DISTINCT i.hostid FROM interface i WHERE '.dbConditionInt('i.interfaceid', $interfaceidsWithoutHostIds));
743			while ($hostData = DBfetch($dbResult)) {
744				$hostIds[$hostData['hostid']] = $hostData['hostid'];
745			}
746		}
747
748		$dbInterfaces = API::HostInterface()->get([
749			'hostids' => $hostIds,
750			'output' => ['hostid', 'main', 'type'],
751			'preservekeys' => true,
752			'nopermissions' => true
753		]);
754
755		// update interfaces from DB with data that will be updated.
756		foreach ($interfaces as $interface) {
757			if (isset($dbInterfaces[$interface['interfaceid']])) {
758				$dbInterfaces[$interface['interfaceid']] = array_merge(
759					$dbInterfaces[$interface['interfaceid']],
760					$interfaces[$interface['interfaceid']]
761				);
762			}
763		}
764
765		$this->checkMainInterfaces($dbInterfaces);
766	}
767
768	private function checkMainInterfacesOnDelete(array $interfaceIds) {
769		$this->checkIfInterfaceHasItems($interfaceIds);
770
771		$hostids = [];
772		$dbResult = DBselect('SELECT DISTINCT i.hostid FROM interface i WHERE '.dbConditionInt('i.interfaceid', $interfaceIds));
773		while ($hostData = DBfetch($dbResult)) {
774			$hostids[$hostData['hostid']] = $hostData['hostid'];
775		}
776
777		$dbInterfaces = API::HostInterface()->get([
778			'hostids' => $hostids,
779			'output' => ['hostid', 'main', 'type'],
780			'preservekeys' => true,
781			'nopermissions' => true
782		]);
783
784		foreach ($interfaceIds as $interfaceId) {
785			unset($dbInterfaces[$interfaceId]);
786		}
787
788		$this->checkMainInterfaces($dbInterfaces);
789	}
790
791	/**
792	 * Check if main interfaces are correctly set for every interface type.
793	 * Each host must either have only one main interface for each interface type, or have no interface of that type at all.
794	 *
795	 * @param array $interfaces
796	 */
797	private function checkMainInterfaces(array $interfaces) {
798		$interfaceTypes = [];
799		foreach ($interfaces as $interface) {
800			if (!isset($interfaceTypes[$interface['hostid']])) {
801				$interfaceTypes[$interface['hostid']] = [];
802			}
803
804			if (!isset($interfaceTypes[$interface['hostid']][$interface['type']])) {
805				$interfaceTypes[$interface['hostid']][$interface['type']] = ['main' => 0, 'all' => 0];
806			}
807
808			if ($interface['main'] == INTERFACE_PRIMARY) {
809				$interfaceTypes[$interface['hostid']][$interface['type']]['main']++;
810			}
811			else {
812				$interfaceTypes[$interface['hostid']][$interface['type']]['all']++;
813			}
814		}
815
816		foreach ($interfaceTypes as $interfaceHostId => $interfaceType) {
817			foreach ($interfaceType as $type => $counters) {
818				if ($counters['all'] && !$counters['main']) {
819					$host = API::Host()->get([
820						'hostids' => $interfaceHostId,
821						'output' => ['name'],
822						'preservekeys' => true,
823						'nopermissions' => true
824					]);
825					$host = reset($host);
826
827					self::exception(ZBX_API_ERROR_PARAMETERS,
828						_s('No default interface for "%1$s" type on "%2$s".', hostInterfaceTypeNumToName($type), $host['name']));
829				}
830
831				if ($counters['main'] > 1) {
832					self::exception(ZBX_API_ERROR_PARAMETERS, _('Host cannot have more than one default interface of the same type.'));
833				}
834			}
835		}
836	}
837
838	private function checkIfInterfaceHasItems(array $interfaceIds) {
839		$items = API::Item()->get([
840			'output' => ['name'],
841			'selectHosts' => ['name'],
842			'interfaceids' => $interfaceIds,
843			'preservekeys' => true,
844			'nopermissions' => true,
845			'limit' => 1
846		]);
847
848		foreach ($items as $item) {
849			$host = reset($item['hosts']);
850
851			self::exception(ZBX_API_ERROR_PARAMETERS,
852				_s('Interface is linked to item "%1$s" on "%2$s".', $item['name'], $host['name']));
853		}
854	}
855
856	protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) {
857		$sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts);
858
859		if ($options['countOutput'] === null && $options['selectHosts'] !== null) {
860			$sqlParts = $this->addQuerySelect('hi.hostid', $sqlParts);
861		}
862
863		return $sqlParts;
864	}
865
866	protected function addRelatedObjects(array $options, array $result) {
867		$result = parent::addRelatedObjects($options, $result);
868
869		$interfaceIds = array_keys($result);
870
871		// adding hosts
872		if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) {
873			$relationMap = $this->createRelationMap($result, 'interfaceid', 'hostid');
874			$hosts = API::Host()->get([
875				'output' => $options['selectHosts'],
876				'hosts' => $relationMap->getRelatedIds(),
877				'preservekeys' => true
878			]);
879			$result = $relationMap->mapMany($result, $hosts, 'hosts');
880		}
881
882		// adding items
883		if ($options['selectItems'] !== null) {
884			if ($options['selectItems'] != API_OUTPUT_COUNT) {
885				$items = API::Item()->get([
886					'output' => $this->outputExtend($options['selectItems'], ['itemid', 'interfaceid']),
887					'interfaceids' => $interfaceIds,
888					'nopermissions' => true,
889					'preservekeys' => true,
890					'filter' => ['flags' => null]
891				]);
892				$relationMap = $this->createRelationMap($items, 'interfaceid', 'itemid');
893
894				$items = $this->unsetExtraFields($items, ['interfaceid', 'itemid'], $options['selectItems']);
895				$result = $relationMap->mapMany($result, $items, 'items', $options['limitSelects']);
896			}
897			else {
898				$items = API::Item()->get([
899					'interfaceids' => $interfaceIds,
900					'nopermissions' => true,
901					'filter' => ['flags' => null],
902					'countOutput' => true,
903					'groupCount' => true
904				]);
905				$items = zbx_toHash($items, 'interfaceid');
906				foreach ($result as $interfaceId => $interface) {
907					$result[$interfaceId]['items'] = isset($items[$interfaceId]) ? $items[$interfaceId]['rowscount'] : 0;
908				}
909			}
910		}
911
912		return $result;
913	}
914}
915