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