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 shares common properties, constants and methods for different controllers used for item tests.
24 */
25abstract class CControllerPopupItemTest extends CController {
26	/**
27	 * Types of preprocessing tests, depending on type of item.
28	 */
29	const ZBX_TEST_TYPE_ITEM = 0;
30	const ZBX_TEST_TYPE_ITEM_PROTOTYPE = 1;
31	const ZBX_TEST_TYPE_LLD = 2;
32
33	/**
34	 * Max-length of input fields that can contain resolved macro values. Used in views for input fields.
35	 *
36	 * @var int
37	 */
38	public const INPUT_MAX_LENGTH = 2048;
39
40	/**
41	 * Define a set of item types allowed to test and item properties needed to collect for each item type.
42	 *
43	 * @var array
44	 */
45	private static $testable_item_types = [ITEM_TYPE_ZABBIX, ITEM_TYPE_SIMPLE, ITEM_TYPE_INTERNAL, ITEM_TYPE_EXTERNAL,
46		ITEM_TYPE_DB_MONITOR, ITEM_TYPE_HTTPAGENT, ITEM_TYPE_SSH, ITEM_TYPE_TELNET, ITEM_TYPE_JMX,
47		ITEM_TYPE_CALCULATED, ITEM_TYPE_SNMP, ITEM_TYPE_SCRIPT
48	];
49
50	/**
51	 * Item value type used if user has not specified one.
52	 */
53	const ZBX_DEFAULT_VALUE_TYPE = ITEM_VALUE_TYPE_TEXT;
54
55	/**
56	 * Item types requiring interface.
57	 *
58	 * @var array
59	 */
60	protected $items_require_interface = [
61		ITEM_TYPE_ZABBIX => [
62			'address' => true,
63			'port' => true
64		],
65		ITEM_TYPE_IPMI => [
66			'address' => true,
67			'port' => true
68		],
69		ITEM_TYPE_SIMPLE => [
70			'address' => true,
71			'port' => false
72		],
73		ITEM_TYPE_SNMP => [
74			'address' => true,
75			'port' => true
76		],
77		ITEM_TYPE_SSH => [
78			'address' => true,
79			'port' => false
80		],
81		ITEM_TYPE_TELNET => [
82			'address' => true,
83			'port' => false
84		]
85	];
86
87	/**
88	 * Item types with proxy support.
89	 *
90	 * @var array
91	 */
92	protected $items_support_proxy = [ITEM_TYPE_ZABBIX, ITEM_TYPE_SIMPLE, ITEM_TYPE_INTERNAL, ITEM_TYPE_EXTERNAL,
93		ITEM_TYPE_DB_MONITOR, ITEM_TYPE_HTTPAGENT, ITEM_TYPE_IPMI, ITEM_TYPE_SSH, ITEM_TYPE_TELNET, ITEM_TYPE_JMX,
94		ITEM_TYPE_SNMP, ITEM_TYPE_SCRIPT
95	];
96
97	/**
98	 * Item types with mandatory item key.
99	 *
100	 * @var array
101	 */
102	protected $item_types_has_key_mandatory = [ITEM_TYPE_ZABBIX, ITEM_TYPE_SIMPLE, ITEM_TYPE_INTERNAL,
103		ITEM_TYPE_EXTERNAL, ITEM_TYPE_DB_MONITOR, ITEM_TYPE_HTTPAGENT, ITEM_TYPE_IPMI,
104		ITEM_TYPE_SSH, ITEM_TYPE_TELNET, ITEM_TYPE_JMX, ITEM_TYPE_CALCULATED
105	];
106
107	/**
108	 * Item properties where macros are supported.
109	 *
110	 * @var array
111	 */
112	protected $macros_by_item_props = [
113		'key' => [
114			'host' => ['{HOSTNAME}', '{HOST.HOST}', '{HOST.NAME}'],
115			'interface' => ['{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}', '{HOST.CONN}', '{HOST.PORT}'],
116			'support_user_macros' => true,
117			'support_lld_macros' => true
118		],
119		'params_es' => [
120			'host' => ['{HOSTNAME}', '{HOST.HOST}', '{HOST.NAME}'],
121			'interface' => ['{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}', '{HOST.CONN}', '{HOST.PORT}'],
122			'support_user_macros' => true,
123			'support_lld_macros' => true
124		],
125		'params_ap' => [
126			'host' => ['{HOSTNAME}', '{HOST.HOST}', '{HOST.NAME}'],
127			'interface' => ['{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}', '{HOST.CONN}', '{HOST.PORT}'],
128			'support_user_macros' => true,
129			'support_lld_macros' => true
130		],
131		'jmx_endpoint' => [
132			'host' => ['{HOSTNAME}', '{HOST.HOST}', '{HOST.NAME}'],
133			'interface' => ['{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}', '{HOST.CONN}', '{HOST.PORT}'],
134			'support_user_macros' => true,
135			'support_lld_macros' => true
136		],
137		'url' => [
138			'host' => ['{HOSTNAME}', '{HOST.HOST}', '{HOST.NAME}'],
139			'interface' => ['{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}', '{HOST.CONN}', '{HOST.PORT}'],
140			'item' => ['{ITEM.ID}', '{ITEM.KEY.ORIG}', '{ITEM.KEY}'],
141			'support_user_macros' => true,
142			'support_lld_macros' => true
143		],
144		'posts' => [
145			'host' => ['{HOSTNAME}', '{HOST.HOST}', '{HOST.NAME}'],
146			'interface' => ['{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}', '{HOST.CONN}', '{HOST.PORT}'],
147			'item' => ['{ITEM.ID}', '{ITEM.KEY.ORIG}', '{ITEM.KEY}'],
148			'support_user_macros' => true,
149			'support_lld_macros' => true
150		],
151		'http_proxy' => [
152			'host' => ['{HOSTNAME}', '{HOST.HOST}', '{HOST.NAME}'],
153			'interface' => ['{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}', '{HOST.CONN}', '{HOST.PORT}'],
154			'item' => ['{ITEM.ID}', '{ITEM.KEY.ORIG}', '{ITEM.KEY}'],
155			'support_user_macros' => true,
156			'support_lld_macros' => true
157		],
158		'ssl_cert_file' => [
159			'host' => ['{HOSTNAME}', '{HOST.HOST}', '{HOST.NAME}'],
160			'interface' => ['{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}', '{HOST.CONN}', '{HOST.PORT}'],
161			'item' => ['{ITEM.ID}', '{ITEM.KEY.ORIG}', '{ITEM.KEY}'],
162			'support_user_macros' => true,
163			'support_lld_macros' => true
164		],
165		'ssl_key_file' => [
166			'host' => ['{HOSTNAME}', '{HOST.HOST}', '{HOST.NAME}'],
167			'interface' => ['{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}', '{HOST.CONN}', '{HOST.PORT}'],
168			'item' => ['{ITEM.ID}', '{ITEM.KEY.ORIG}', '{ITEM.KEY}'],
169			'support_user_macros' => true,
170			'support_lld_macros' => true
171		],
172		'query_fields' => [
173			'host' => ['{HOSTNAME}', '{HOST.HOST}', '{HOST.NAME}'],
174			'interface' => ['{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}', '{HOST.CONN}', '{HOST.PORT}'],
175			'item' => ['{ITEM.ID}', '{ITEM.KEY.ORIG}', '{ITEM.KEY}'],
176			'support_user_macros' => true,
177			'support_lld_macros' => true
178		],
179		'headers' => [
180			'host' => ['{HOSTNAME}', '{HOST.HOST}', '{HOST.NAME}'],
181			'interface' => ['{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}', '{HOST.CONN}', '{HOST.PORT}'],
182			'item' => ['{ITEM.ID}', '{ITEM.KEY.ORIG}', '{ITEM.KEY}'],
183			'support_user_macros' => true,
184			'support_lld_macros' => true
185		],
186		'parameters' => [
187			'host' => ['{HOSTNAME}', '{HOST.HOST}', '{HOST.NAME}'],
188			'interface' => ['{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}', '{HOST.CONN}'],
189			'item' => ['{ITEM.ID}', '{ITEM.KEY.ORIG}', '{ITEM.KEY}'],
190			'support_user_macros' => true,
191			'support_lld_macros' => true
192		],
193		'params_f' => [],
194		'script' => [
195			'support_user_macros' => true,
196			'support_lld_macros' => true
197		],
198		'timeout' => [
199			'support_user_macros' => true,
200			'support_lld_macros' => true
201		],
202		'ipmi_sensor' => [
203			'support_user_macros' => false,
204			'support_lld_macros' => true
205		],
206		'snmp_oid' => [
207			'support_user_macros' => true,
208			'support_lld_macros' => true
209		],
210		'username' => [
211			'support_user_macros' => true,
212			'support_lld_macros' => true
213		],
214		'password' => [
215			'support_user_macros' => true,
216			'support_lld_macros' => true
217		],
218		'http_username' => [
219			'support_user_macros' => true,
220			'support_lld_macros' => true
221		],
222		'http_password' => [
223			'support_user_macros' => true,
224			'support_lld_macros' => true
225		]
226	];
227
228	/**
229	 * Tested item type.
230	 *
231	 * @var int
232	 */
233	protected $item_type;
234
235	/**
236	 * Tested item's host.
237	 *
238	 * @var array
239	 */
240	protected $host;
241
242	/**
243	 * Is item testable.
244	 *
245	 * @var bool
246	 */
247	protected $is_item_testable;
248
249	/**
250	 * @var object
251	 */
252	protected $preproc_item;
253
254	/**
255	 * @var array
256	 */
257	protected static $preproc_steps_using_prev_value = [ZBX_PREPROC_DELTA_VALUE, ZBX_PREPROC_DELTA_SPEED,
258		ZBX_PREPROC_THROTTLE_VALUE, ZBX_PREPROC_THROTTLE_TIMED_VALUE
259	];
260
261	/**
262	 * @var int
263	 */
264	protected $eol;
265
266	/**
267	 * Get testable item types based on host type.
268	 *
269	 * @param string $hostid
270	 *
271	 * @return array
272	 */
273	public static function getTestableItemTypes(string $hostid = '0'): array {
274		if ($hostid != 0 && self::isItemTypeTestable($hostid)) {
275			self::$testable_item_types[] = ITEM_TYPE_IPMI;
276		}
277
278		return self::$testable_item_types;
279	}
280
281	/**
282	 * Function checks if item type can be tested depending on what type of host it belongs to.
283	 *
284	 * @param string $hostid
285	 *
286	 * @return bool
287	 */
288	protected static function isItemTypeTestable(string $hostid): bool {
289		$ret = (bool) API::Template()->get([
290			'countOutput' => true,
291			'templateids' => [$hostid]
292		]);
293
294		return !$ret;
295	}
296
297	protected function checkPermissions() {
298		$ret = ($this->getUserType() >= USER_TYPE_ZABBIX_ADMIN);
299
300		/*
301		 * Preprocessing test can be done from mass-update section so host is non mandatory but if it is used, it must
302		 * be editable.
303		 */
304		$hostid = $this->getInput('hostid', 0);
305
306		if ($ret && $hostid != 0) {
307			$hosts = API::Host()->get([
308				'output' => ['hostid', 'host', 'name', 'status', 'proxy_hostid', 'tls_subject', 'maintenance_status',
309					'maintenance_type', 'ipmi_authtype', 'ipmi_privilege', 'ipmi_username', 'ipmi_password',
310					'tls_issuer', 'tls_connect'
311				],
312				'hostids' => [$hostid],
313				'editable' => true
314			]);
315
316			if (!$hosts) {
317				$hosts = API::Template()->get([
318					'output' => ['templateid', 'host', 'name', 'status'],
319					'templateids' => [$hostid],
320					'editable' => true
321				]);
322
323				$hosts[0] = CArrayHelper::renameKeys($hosts[0], ['templateid' => 'hostid']);
324			}
325
326			$this->host = reset($hosts);
327
328			return (bool) $this->host;
329		}
330
331		return $ret;
332	}
333
334	/**
335	 * Function returns instance of item, item prototype or discovery rule class.
336	 *
337	 * @param int $test_type
338	 *
339	 * @return CItem|CItemPrototype|CDiscoveryRule
340	 */
341	protected static function getPreprocessingItemClassInstance($test_type) {
342		switch ($test_type) {
343			case self::ZBX_TEST_TYPE_ITEM:
344				return new CItem;
345
346			case self::ZBX_TEST_TYPE_ITEM_PROTOTYPE:
347				return new CItemPrototype;
348
349			case self::ZBX_TEST_TYPE_LLD:
350				return new CDiscoveryRule;
351		}
352	}
353
354	/**
355	 * Function returns list of proxies.
356	 *
357	 * @return array
358	 */
359	protected function getHostProxies() {
360		$proxies = API::Proxy()->get([
361			'output' => ['host'],
362			'preservekeys' => true
363		]);
364
365		CArrayHelper::sort($proxies, [['field' => 'host', 'order' => ZBX_SORT_UP]]);
366
367		foreach ($proxies as &$proxy) {
368			$proxy = $proxy['host'];
369		}
370		unset($proxy);
371
372		return $proxies;
373	}
374
375	/**
376	 * Function returns array of item specific properties used for item testing.
377	 *
378	 * @param array $input       Stored user input used to overwrite values retrieved from database.
379	 * @param bool  $for_server  Whether need to add to result an additional properties used only for connection with
380	 *                           Zabbix server.
381	 *
382	 * @return array
383	 */
384	protected function getItemTestProperties(array $input, bool $for_server = false) {
385		$data = [
386			'value_type' => $input['value_type']
387		];
388
389		if (!$this->is_item_testable) {
390			return $data;
391		}
392
393		$data['type'] = $this->item_type;
394
395		if (array_key_exists('itemid', $input)) {
396			$data['itemid'] = $input['itemid'];
397		}
398
399		if (array_key_exists('interface', $input) && array_key_exists('interfaceid', $input['interface'])) {
400			$interface_input['interfaceid'] = $input['interface']['interfaceid'];
401		}
402		elseif (array_key_exists('interfaceid', $input)) {
403			$interface_input['interfaceid'] = $input['interfaceid'];
404		}
405		else {
406			$interface_input['interfaceid'] = 0;
407		}
408
409		if (array_key_exists('interface', $input) && array_key_exists('useip', $input['interface'])) {
410			$interface_input['useip'] = $input['interface']['useip'];
411		}
412
413		if (array_key_exists('data', $input) && array_key_exists('port', $input['data'])) {
414			$interface_input['port'] = $input['data']['port'];
415		}
416		elseif (array_key_exists('interface', $input) && array_key_exists('port', $input['interface'])) {
417			$interface_input['port'] = $input['interface']['port'];
418		}
419
420		if (array_key_exists('data', $input) && array_key_exists('address', $input['data'])) {
421			$interface_input['address'] = $input['data']['address'];
422		}
423		elseif (array_key_exists('interface', $input) && array_key_exists('address', $input['interface'])) {
424			$interface_input['address'] = $input['interface']['address'];
425		}
426		elseif (array_key_exists('address', $input)) {
427			$interface_input['address'] = $input['address'];
428		}
429
430		if (array_key_exists('data', $input) && array_key_exists('interface_details', $input['data'])
431				&& is_array($input['data']['interface_details'])) {
432			$interface_input['details'] = $input['data']['interface_details'];
433		}
434		elseif (array_key_exists('interface', $input) && array_key_exists('details', $input['interface'])) {
435			$interface_input['details'] = $input['interface']['details'];
436		}
437
438		// Set proxy.
439		if (in_array($this->item_type, $this->items_support_proxy)) {
440			if (array_key_exists('data', $input) && array_key_exists('proxy_hostid', $input['data'])) {
441				$data['proxy_hostid'] = $input['data']['proxy_hostid'];
442			}
443			elseif (array_key_exists('proxy_hostid', $input)) {
444				$data['proxy_hostid'] = $input['proxy_hostid'];
445			}
446			elseif (array_key_exists('proxy_hostid', $this->host)) {
447				$data['proxy_hostid'] = $this->host['proxy_hostid'];
448			}
449			else {
450				$data['proxy_hostid'] = 0;
451			}
452		}
453
454		switch ($this->item_type) {
455			case ITEM_TYPE_ZABBIX:
456				$data += [
457					'key' => array_key_exists('key', $input) ? $input['key'] : null,
458					'interface' => $this->getHostInterface($interface_input)
459				];
460
461				if ($this->host['status'] != HOST_STATUS_TEMPLATE) {
462					$data['host'] = [
463						'tls_issuer' => $this->host['tls_issuer'],
464						'tls_connect' => $this->host['tls_connect'],
465						'tls_subject' => $this->host['tls_subject']
466					];
467
468					if ($for_server && $this->host['tls_connect'] == HOST_ENCRYPTION_PSK) {
469						$hosts = API::Host()->get([
470							'output' => ['tls_psk_identity', 'tls_psk'],
471							'hostids' => $this->host['hostid'],
472							'editable' => true
473						]);
474						$host = reset($hosts);
475
476						$data['host']['tls_psk_identity'] = $host['tls_psk_identity'];
477						$data['host']['tls_psk'] = $host['tls_psk'];
478					}
479				}
480
481				unset($data['interface']['useip'], $data['interface']['interfaceid'], $data['interface']['ip'],
482					$data['interface']['dns']
483				);
484				break;
485
486			case ITEM_TYPE_SNMP:
487				if (!array_key_exists('flags', $input)) {
488					$items = (array_key_exists('itemid', $input))
489						? API::Item()->get([
490							'output' => ['flags'],
491							'itemids' => $input['itemid']
492						])
493						: [];
494
495					if ($items) {
496						$item_flag = $items[0]['flags'];
497					}
498					else {
499						switch ($this->getInput('test_type')) {
500							case self::ZBX_TEST_TYPE_LLD:
501								$item_flag = ZBX_FLAG_DISCOVERY_RULE;
502								break;
503
504							case self::ZBX_TEST_TYPE_ITEM_PROTOTYPE;
505								$item_flag = ZBX_FLAG_DISCOVERY_PROTOTYPE;
506								break;
507
508							default:
509								$item_flag = ZBX_FLAG_DISCOVERY_NORMAL;
510								break;
511						}
512					}
513				}
514				else {
515					$item_flag = $input['flags'];
516				}
517
518				$data += [
519					'snmp_oid' => array_key_exists('snmp_oid', $input) ? $input['snmp_oid'] : null,
520					'flags' => $item_flag,
521					'host' => [
522						'host' => $this->host['host']
523					],
524					'interface' => $this->getHostInterface($interface_input)
525				];
526
527				unset($data['interface']['ip'], $data['interface']['dns']);
528				break;
529
530			case ITEM_TYPE_INTERNAL:
531				$data += [
532					'key' => $input['key'],
533					'host' => [
534						'hostid' => $this->host['hostid']
535					]
536				];
537
538				if ($this->host['status'] != HOST_STATUS_TEMPLATE) {
539					$data['host'] += [
540						'maintenance_status' => $this->host['maintenance_status'],
541						'maintenance_type' => $this->host['maintenance_type']
542					];
543				}
544				break;
545
546			case ITEM_TYPE_EXTERNAL:
547				$data += [
548					'key' => $input['key']
549				];
550				break;
551
552			case ITEM_TYPE_DB_MONITOR:
553				$data += [
554					'key' => $input['key'],
555					'params_ap' => array_key_exists('params_ap', $input) ? $input['params_ap'] : null,
556					'username' => array_key_exists('username', $input) ? $input['username'] : null,
557					'password' => array_key_exists('password', $input) ? $input['password'] : null
558				];
559				break;
560
561			case ITEM_TYPE_HTTPAGENT:
562				$data += [
563					'key' => $input['key'],
564					'http_authtype' => array_key_exists('http_authtype', $input)
565						? $input['http_authtype']
566						: HTTPTEST_AUTH_NONE,
567					'follow_redirects' => array_key_exists('follow_redirects', $input) ? $input['follow_redirects'] : 0,
568					'headers' => array_key_exists('headers', $input) ? $input['headers'] : [],
569					'http_proxy' => array_key_exists('http_proxy', $input) ? $input['http_proxy'] : null,
570					'output_format' => array_key_exists('output_format', $input) ? $input['output_format'] : 0,
571					'posts' => array_key_exists('posts', $input) ? $input['posts'] : null,
572					'post_type' => array_key_exists('post_type', $input) ? $input['post_type'] : ZBX_POSTTYPE_RAW,
573					'query_fields' => array_key_exists('query_fields', $input) ? $input['query_fields'] : [],
574					'request_method' => array_key_exists('request_method', $input)
575						? $input['request_method']
576						: HTTPCHECK_REQUEST_GET,
577					'retrieve_mode' => array_key_exists('retrieve_mode', $input)
578						? $input['retrieve_mode']
579						: HTTPTEST_STEP_RETRIEVE_MODE_CONTENT,
580					'ssl_cert_file' => array_key_exists('ssl_cert_file', $input) ? $input['ssl_cert_file'] : null,
581					'ssl_key_file' => array_key_exists('ssl_key_file', $input) ? $input['ssl_key_file'] : null,
582					'ssl_key_password' => array_key_exists('ssl_key_password', $input)
583						? $input['ssl_key_password']
584						: null,
585					'status_codes' => array_key_exists('status_codes', $input) ? $input['status_codes'] : null,
586					'timeout' => array_key_exists('timeout', $input) ? $input['timeout'] : null,
587					'url' => array_key_exists('url', $input) ? $input['url'] : null,
588					'verify_host' => array_key_exists('verify_host', $input) ? $input['verify_host'] : 0,
589					'verify_peer' => array_key_exists('verify_peer', $input) ? $input['verify_peer'] : 0
590				];
591
592				if ($data['http_authtype'] != HTTPTEST_AUTH_NONE) {
593					$data += [
594						'http_username' => array_key_exists('http_username', $input) ? $input['http_username'] : null,
595						'http_password' => array_key_exists('http_password', $input) ? $input['http_password'] : null
596					];
597				}
598				break;
599
600			case ITEM_TYPE_IPMI:
601				$data += [
602					'key' => $input['key'],
603					'ipmi_sensor' => array_key_exists('ipmi_sensor', $input) ? $input['ipmi_sensor'] : null,
604					'interface' => $this->getHostInterface($interface_input),
605					'host' => [
606						'hostid' => $this->host['hostid']
607					]
608				];
609
610				if ($this->host['status'] != HOST_STATUS_TEMPLATE) {
611					$data['host'] += [
612						'ipmi_authtype' => $this->host['ipmi_authtype'],
613						'ipmi_privilege' => $this->host['ipmi_privilege'],
614						'ipmi_username' => $this->host['ipmi_username'],
615						'ipmi_password' => $this->host['ipmi_password']
616					];
617				}
618
619				unset($data['interface']['useip'], $data['interface']['interfaceid'], $data['interface']['ip'],
620					$data['interface']['dns']
621				);
622				break;
623
624			case ITEM_TYPE_SSH:
625				$data += [
626					'key' => $input['key'],
627					'authtype' => array_key_exists('authtype', $input) ? $input['authtype'] : ITEM_AUTHTYPE_PASSWORD,
628					'params_es' => array_key_exists('params_es', $input) ? $input['params_es'] : ITEM_AUTHTYPE_PASSWORD,
629					'username' => array_key_exists('username', $input) ? $input['username'] : null,
630					'password' => array_key_exists('password', $input) ? $input['password'] : null,
631					'interface' => $this->getHostInterface($interface_input)
632				];
633
634				if ($data['authtype'] == ITEM_AUTHTYPE_PUBLICKEY) {
635					$data += [
636						'publickey' => array_key_exists('publickey', $input) ? $input['publickey'] : null,
637						'privatekey' => array_key_exists('privatekey', $input) ? $input['privatekey'] : null
638					];
639				}
640				break;
641
642			case ITEM_TYPE_TELNET:
643				$data += [
644					'key' => $input['key'],
645					'params_es' => array_key_exists('params_es', $input) ? $input['params_es'] : null,
646					'username' => array_key_exists('username', $input) ? $input['username'] : null,
647					'password' => array_key_exists('password', $input) ? $input['password'] : null,
648					'interface' => $this->getHostInterface($interface_input)
649				];
650				break;
651
652			case ITEM_TYPE_JMX:
653				$data += [
654					'key' => $input['key'],
655					'jmx_endpoint' => array_key_exists('jmx_endpoint', $input) ? $input['jmx_endpoint'] : null,
656					'username' => array_key_exists('username', $input) ? $input['username'] : null,
657					'password' => array_key_exists('password', $input) ? $input['password'] : null
658				];
659				break;
660
661			case ITEM_TYPE_CALCULATED:
662				$data += [
663					'key' => $input['key'],
664					'params_f' => array_key_exists('params_f', $input) ? $input['params_f'] : null,
665					'host' => [
666						'host' => $this->host['host']
667					]
668				];
669				break;
670
671			case ITEM_TYPE_SIMPLE:
672				$data += [
673					'key' => $input['key'],
674					'interface' => $this->getHostInterface($interface_input),
675					'username' => array_key_exists('username', $input) ? $input['username'] : null,
676					'password' => array_key_exists('password', $input) ? $input['password'] : null
677				];
678
679				unset($data['interface']['useip'], $data['interface']['interfaceid'], $data['interface']['ip'],
680					$data['interface']['dns'],  $data['interface']['port']
681				);
682				break;
683
684			case ITEM_TYPE_SCRIPT:
685				$data += [
686					'key' => $input['key'],
687					'parameters' => array_key_exists('parameters', $input) ? $input['parameters'] : [],
688					'script' => array_key_exists('script', $input) ? $input['script'] : null,
689					'timeout' => array_key_exists('timeout', $input) ? $input['timeout'] : null
690				];
691				break;
692		}
693
694		return $data;
695	}
696
697	/**
698	 * Check if item belongs to host and select and resolve interface properties. Leave fields empty otherwise.
699	 *
700	 * @param array $inputs  Stored user input used to overwrite values retrieved from database.
701	 *
702	 * @return array $interface_data
703	 */
704	protected function getHostInterface(array $inputs) {
705		$interface_data = [
706			'address' => '',
707			'port' => '',
708			'interfaceid' => 0,
709			'type' => INTERFACE_TYPE_UNKNOWN,
710			'ip' => '',
711			'dns' => '',
712			'useip' => INTERFACE_USE_DNS,
713			'details' => [
714				'community' => '',
715				'version' => SNMP_V2C,
716				'securityname' => '',
717				'securitylevel' => ITEM_SNMPV3_SECURITYLEVEL_NOAUTHNOPRIV,
718				'authpassphrase' => '',
719				'privpassphrase' => '',
720				'authprotocol' => ITEM_SNMPV3_AUTHPROTOCOL_MD5,
721				'privprotocol' => ITEM_SNMPV3_PRIVPROTOCOL_DES,
722				'contextname' => ''
723			]
724		];
725
726		if ($this->item_type != ITEM_TYPE_SNMP) {
727			unset($interface_data['details'], $inputs['details']);
728		}
729
730		// Get values from database; resolve macros.
731		if (($this->host['status'] == HOST_STATUS_MONITORED || $this->host['status'] == HOST_STATUS_NOT_MONITORED)
732				&& array_key_exists('interfaceid', $inputs)) {
733			$output = ['hostid', 'type', 'dns', 'ip', 'port', 'main', 'useip'];
734			$interfaces = [];
735
736			if ($this->item_type == ITEM_TYPE_SNMP) {
737				$output[] = 'details';
738			}
739
740			if (itemTypeInterface($this->item_type) === false) {
741				$host_interfaces = API::HostInterface()->get([
742					'output' => $output,
743					'hostids' => $this->host['hostid'],
744					'filter' => ['main' => INTERFACE_PRIMARY]
745				]);
746				$host_interfaces = zbx_toHash($host_interfaces, 'type');
747
748				$ordered_interface_types = [INTERFACE_TYPE_AGENT, INTERFACE_TYPE_SNMP, INTERFACE_TYPE_JMX,
749					INTERFACE_TYPE_IPMI
750				];
751
752				foreach ($ordered_interface_types as $interface_type) {
753					if (array_key_exists($interface_type, $host_interfaces)) {
754						$interfaces[] = $host_interfaces[$interface_type];
755						break;
756					}
757				}
758			}
759			else {
760				$interfaces = API::HostInterface()->get([
761					'output' => $output,
762					'interfaceids' => $inputs['interfaceid'],
763					'hostids' => $this->host['hostid']
764				]);
765			}
766
767			if (count($interfaces) != 0) {
768				$interfaces = CMacrosResolverHelper::resolveHostInterfaces($interfaces);
769				$interface_data = ($this->item_type == ITEM_TYPE_SNMP)
770					? ['details' => $interfaces[0]['details'] + $interface_data['details']]
771					: [];
772
773				$interface_data += [
774					'address' => ($interfaces[0]['useip'] == INTERFACE_USE_IP)
775						? $interfaces[0]['ip']
776						: $interfaces[0]['dns'],
777					'port' => $interfaces[0]['port'],
778					'useip' => $interfaces[0]['useip'],
779					'type' => $interfaces[0]['type'],
780					'ip' => $interfaces[0]['ip'],
781					'dns' => $interfaces[0]['dns'],
782					'interfaceid' => $interfaces[0]['interfaceid']
783				];
784			}
785		}
786
787		if ($this->item_type == ITEM_TYPE_SCRIPT) {
788			return $interface_data;
789		}
790
791		// Apply client side cache.
792		foreach ($inputs as $key => $value) {
793			if (is_array($value)) {
794				$interface_data[$key] = $value + $interface_data[$key];
795			}
796			else {
797				$interface_data[$key] = $value;
798			}
799		}
800
801		return $interface_data;
802	}
803
804	/**
805	 * Function returns human readable time used for previous time field in item test.
806	 *
807	 * @return string
808	 */
809	protected function getPrevTime() {
810		$time_change = max($this->getInput('time_change', 1), 1);
811
812		if ($time_change >= SEC_PER_DAY) {
813			$n = floor($time_change / SEC_PER_DAY);
814			return 'now-'.$n.'d';
815		}
816		elseif ($time_change >= SEC_PER_HOUR * 5) {
817			$n = floor($time_change / SEC_PER_HOUR);
818			return 'now-'.$n.'h';
819		}
820		elseif ($time_change >= SEC_PER_MIN * 5) {
821			$n = floor($time_change / SEC_PER_MIN);
822			return 'now-'.$n.'m';
823		}
824		else {
825			return 'now-'.$time_change.'s';
826		}
827	}
828
829	/**
830	 * Function to unset unspecified values before sending 'get value' request to server.
831	 *
832	 * @param array $data  Data array containing all parameters prepared to be sent to server.
833	 *
834	 * @return array
835	 */
836	protected function unsetEmptyValues(array $data) {
837		foreach ($data as $key => $value) {
838			if ($key === 'host' && is_array($value)) {
839				$data[$key] = $this->unsetEmptyValues($value);
840
841				if (!$data[$key]) {
842					unset($data[$key]);
843				}
844			}
845			elseif ($key === 'interface' && $this->item_type == ITEM_TYPE_SNMP) {
846				if ($data['interface']['details']['version'] == SNMP_V3) {
847					unset($data['interface']['details']['community']);
848
849					if ($data['interface']['details']['securitylevel'] == ITEM_SNMPV3_SECURITYLEVEL_NOAUTHNOPRIV) {
850						unset($data['interface']['details']['authprotocol'],
851							$data['interface']['details']['authpassphrase'],
852							$data['interface']['details']['privprotocol'],
853							$data['interface']['details']['privpassphrase']
854						);
855					}
856					elseif ($data['interface']['details']['securitylevel'] == ITEM_SNMPV3_SECURITYLEVEL_AUTHNOPRIV) {
857						unset($data['interface']['details']['privprotocol'],
858							$data['interface']['details']['privpassphrase']
859						);
860					}
861				}
862				else {
863					unset($data['interface']['details']['contextname'],
864						$data['interface']['details']['securityname'],
865						$data['interface']['details']['securitylevel'],
866						$data['interface']['details']['authprotocol'],
867						$data['interface']['details']['authpassphrase'],
868						$data['interface']['details']['privprotocol'],
869						$data['interface']['details']['privpassphrase']
870					);
871				}
872
873				unset($data['interface']['type']);
874			}
875			elseif ($key === 'query_fields') {
876				if ($value === '[]') {
877					unset($data[$key]);
878				}
879			}
880			elseif ($key === 'parameters') {
881				if (!$value) {
882					unset($data[$key]);
883				}
884			}
885			elseif ($value === '' || $value === null) {
886				unset($data[$key]);
887			}
888		}
889
890		return $data;
891	}
892
893	/**
894	 * Function returns array containing values for each of supported macros.
895	 *
896	 * @return array
897	 */
898	protected function getSupportedMacros(array $inputs) {
899		$interface = $this->getHostInterface(['interfaceid' => $inputs['interfaceid']]);
900
901		$macros = [
902			'host' => [
903				'{HOSTNAME}' => $this->host['host'],
904				'{HOST.HOST}' => $this->host['host'],
905				'{HOST.NAME}' => $this->host['name']
906			],
907			'interface' => [
908				'{HOST.IP}' => $interface['ip'],
909				'{IPADDRESS}' => $interface['ip'],
910				'{HOST.DNS}' => $interface['dns'],
911				'{HOST.CONN}' => $interface['address'],
912				'{HOST.PORT}' => $interface['port']
913			],
914			'item' => [
915				'{ITEM.ID}' => (array_key_exists('itemid', $inputs) && $inputs['itemid'])
916					? $inputs['itemid']
917					: UNRESOLVED_MACRO_STRING,
918				'{ITEM.KEY}' => array_key_exists('key', $inputs) ? $inputs['key'] : UNRESOLVED_MACRO_STRING,
919				'{ITEM.KEY.ORIG}' => array_key_exists('key', $inputs) ? $inputs['key'] : UNRESOLVED_MACRO_STRING
920			]
921		];
922
923		if (array_key_exists('key', $inputs) && strstr($inputs['key'], '{') !== false) {
924			$usermacros = CMacrosResolverHelper::extractItemTestMacros([
925				'steps' => [],
926				'delay' => '',
927				'supported_macros' => array_diff_key($this->macros_by_item_props['key'],
928					['support_user_macros' => true, 'support_lld_macros' => true]
929				),
930				'support_lldmacros' => ($this->preproc_item instanceof CItemPrototype),
931				'texts_support_macros' => [$inputs['key']],
932				'texts_support_lld_macros' => [$inputs['key']],
933				'texts_support_user_macros' => [$inputs['key']],
934				'hostid' => $this->host ? $this->host['hostid'] : 0,
935				'macros_values' => array_intersect_key($macros, $this->macros_by_item_props['key'])
936			]);
937
938			foreach ($usermacros['macros'] as $macro => $value) {
939				$macros['item']['{ITEM.KEY}'] = str_replace($macro, $value, $macros['item']['{ITEM.KEY}']);
940			}
941		}
942
943		return $macros;
944	}
945
946	/**
947	 * Transform front-end familiar array of http query fields to the form server is capable to handle.
948	 *
949	 * @param array $data
950	 * @param array $data[name]   Indexed array of names.
951	 * @param array $data[value]  Indexed array of values.
952	 *
953	 * @return string
954	 */
955	protected function transformQueryFields(array $data) {
956		$result = [];
957
958		if (array_key_exists('name', $data) && array_key_exists('value', $data)) {
959			foreach (array_keys($data['name']) as $num) {
960				if (array_key_exists($num, $data['value']) && $data['name'][$num] !== '') {
961					$result[] = [$data['name'][$num] => $data['value'][$num]];
962				}
963			}
964		}
965
966		return json_encode($result);
967	}
968
969	/**
970	 * Transform front-end familiar array of parameters fields to the form server is capable to handle. Server expects
971	 * one object where parameter names are keys and parameter values are values. Note that parameter names are unique.
972	 *
973	 * @param array $data
974	 * @param array $data[name]   Indexed array of names.
975	 * @param array $data[value]  Indexed array of values.
976	 *
977	 * @return array
978	 */
979	protected function transformParametersFields(array $data): array {
980		$result = [];
981
982		if (array_key_exists('name', $data) && array_key_exists('value', $data)) {
983			foreach (array_keys($data['name']) as $num) {
984				if (array_key_exists($num, $data['value']) && $data['name'][$num] !== '') {
985					$result += [$data['name'][$num] => $data['value'][$num]];
986				}
987			}
988		}
989
990		return $result;
991	}
992
993	/**
994	 * Transform front-end familiar array of http header fields to the form server is capable to handle.
995	 *
996	 * @param array $data
997	 * @param array $data[name]   Indexed array of names.
998	 * @param array $data[value]  Indexed array of values.
999	 *
1000	 * @return string
1001	 */
1002	protected function transformHeaderFields(array $data) {
1003		$result = [];
1004
1005		if (array_key_exists('name', $data) && array_key_exists('value', $data)) {
1006			foreach (array_keys($data['name']) as $num) {
1007				if (array_key_exists($num, $data['value']) && $data['name'][$num] !== '') {
1008					$result[] = $data['name'][$num].': '.$data['value'][$num];
1009				}
1010			}
1011		}
1012
1013		return implode("\r\n", $result);
1014	}
1015
1016	/**
1017	 * Resolve macros used in preprocessing step parameter fields.
1018	 *
1019	 * @param array $steps  Steps from item test input form.
1020	 *
1021	 * @return array
1022	 */
1023	protected function resolvePreprocessingStepMacros(array $steps) {
1024		// Resolve macros used in parameter fields.
1025		$macros_posted = $this->getInput('macros', []);
1026		$macros_types = ($this->preproc_item instanceof CItemPrototype)
1027			? ['usermacros' => true, 'lldmacros' => true]
1028			: ['usermacros' => true];
1029
1030		foreach ($steps as &$step) {
1031			/*
1032			 * Values received from user input form may be transformed so we must remove redundant "\r" before
1033			 * sending data to Zabbix server.
1034			 */
1035			$step['params'] = str_replace("\r\n", "\n", $step['params']);
1036
1037			// Resolve macros in parameter fields before send data to Zabbix server.
1038			foreach (['params', 'error_handler_params'] as $field) {
1039				$matched_macros = (new CMacrosResolverGeneral)->getMacroPositions($step[$field], $macros_types);
1040
1041				foreach (array_reverse($matched_macros, true) as $pos => $macro) {
1042					$macro_value = array_key_exists($macro, $macros_posted)
1043						? $macros_posted[$macro]
1044						: '';
1045
1046					$step[$field] = substr_replace($step[$field], $macro_value, $pos, strlen($macro));
1047				}
1048			}
1049		}
1050		unset($step);
1051
1052		return $steps;
1053	}
1054
1055	/**
1056	 * Resolve macros used in the calculates item formula.
1057	 *
1058	 * @param string $formula  Calculated item formula.
1059	 *
1060	 * @return array
1061	 */
1062	private function resolveCalcFormulaMacros(string $formula) {
1063		$macros_posted = $this->getInput('macros', []);
1064
1065		if (!$macros_posted) {
1066			return $formula;
1067		}
1068
1069		$expression_parser = new CExpressionParser([
1070			'usermacros' => true,
1071			'lldmacros' => ($this->preproc_item instanceof CItemPrototype),
1072			'calculated' => true,
1073			'host_macro' => true,
1074			'empty_host' => true
1075		]);
1076
1077		if ($expression_parser->parse($formula) != CParser::PARSE_SUCCESS) {
1078			// Cannot parse a calculated item formula. Return as is.
1079			return $formula;
1080		}
1081
1082		$expression = [];
1083		$pos_left = 0;
1084
1085		$tokens = $expression_parser->getResult()->getTokensOfTypes([
1086			CExpressionParserResult::TOKEN_TYPE_USER_MACRO,
1087			CExpressionParserResult::TOKEN_TYPE_LLD_MACRO,
1088			CExpressionParserResult::TOKEN_TYPE_STRING,
1089			CExpressionParserResult::TOKEN_TYPE_HIST_FUNCTION
1090		]);
1091		foreach ($tokens as $token) {
1092			switch ($token['type']) {
1093				case CExpressionParserResult::TOKEN_TYPE_USER_MACRO:
1094				case CExpressionParserResult::TOKEN_TYPE_LLD_MACRO:
1095					if ($pos_left != $token['pos']) {
1096						$expression[] = substr($formula, $pos_left, $token['pos'] - $pos_left);
1097					}
1098					$pos_left = $token['pos'] + $token['length'];
1099
1100					$expression[] = array_key_exists($token['match'], $macros_posted)
1101						? CExpressionParser::quoteString($macros_posted[$token['match']], false)
1102						: $token['match'];
1103					break;
1104
1105				case CExpressionParserResult::TOKEN_TYPE_STRING:
1106					if ($pos_left != $token['pos']) {
1107						$expression[] = substr($formula, $pos_left, $token['pos'] - $pos_left);
1108					}
1109					$pos_left = $token['pos'] + $token['length'];
1110
1111					$string = strtr(CExpressionParser::unquoteString($token['match']), $macros_posted);
1112					$expression[] = CExpressionParser::quoteString($string, false, true);
1113					break;
1114
1115				case CExpressionParserResult::TOKEN_TYPE_HIST_FUNCTION:
1116					foreach ($token['data']['parameters'][0]['data']['filter']['tokens'] as $filter_token) {
1117						switch ($filter_token['type']) {
1118							case CFilterParser::TOKEN_TYPE_USER_MACRO:
1119							case CFilterParser::TOKEN_TYPE_LLD_MACRO:
1120								if ($pos_left != $filter_token['pos']) {
1121									$expression[] = substr($formula, $pos_left, $filter_token['pos'] - $pos_left);
1122								}
1123								$pos_left = $filter_token['pos'] + $filter_token['length'];
1124
1125								$string = strtr($filter_token['match'], $macros_posted);
1126								$expression[] = CFilterParser::quoteString($string);
1127								break;
1128
1129							case CFilterParser::TOKEN_TYPE_STRING:
1130								if ($pos_left != $filter_token['pos']) {
1131									$expression[] = substr($formula, $pos_left, $filter_token['pos'] - $pos_left);
1132								}
1133								$pos_left = $filter_token['pos'] + $filter_token['length'];
1134
1135								$string = strtr(CFilterParser::unquoteString($filter_token['match']), $macros_posted);
1136								$expression[] = CFilterParser::quoteString($string);
1137								break;
1138						}
1139					}
1140					break;
1141			}
1142		}
1143
1144		if ($pos_left != strlen($formula)) {
1145			$expression[] = substr($formula, $pos_left);
1146		}
1147
1148		return implode('', $expression);
1149	}
1150
1151	/**
1152	 * Resolve macros used in item property fields.
1153	 *
1154	 * @param array $inputs  Item fields potentially having supported macros.
1155	 *
1156	 * @return array
1157	 */
1158	protected function resolveItemPropertyMacros(array $inputs) {
1159		// Resolve macros used in parameter fields.
1160		$macros_posted = $this->getInput('macros', []);
1161
1162		foreach (array_keys($this->macros_by_item_props) as $field) {
1163			if (!array_key_exists($field, $inputs)) {
1164				continue;
1165			}
1166
1167			// Special processing for calculated item formula.
1168			if ($field === 'params_f') {
1169				$inputs[$field] = $this->resolveCalcFormulaMacros($inputs[$field], $macros_posted);
1170				continue;
1171			}
1172
1173			// Construct array of supported macros.
1174			$types = [
1175				'usermacros' => true,
1176				'macros_n' => []
1177			];
1178
1179			if ($this->preproc_item instanceof CItemPrototype) {
1180				$types += ['lldmacros' => true];
1181			}
1182
1183			foreach (['host', 'interface', 'item'] as $type) {
1184				if (array_key_exists($type, $this->macros_by_item_props[$field])) {
1185					$types['macros_n'] = array_merge($types['macros_n'], $this->macros_by_item_props[$field][$type]);
1186				}
1187			}
1188
1189			// Get strings to resolve and types of supported macros.
1190			if ($field === 'query_fields' || $field === 'headers' || $field === 'parameters') {
1191				if (!array_key_exists($field, $inputs) || !$inputs[$field]) {
1192					continue;
1193				}
1194
1195				foreach (['name', 'value'] as $key) {
1196					foreach (array_keys($inputs[$field][$key]) as $nr) {
1197						$str = &$inputs[$field][$key][$nr];
1198						if (strstr($str, '{') !== false) {
1199							$matched_macros = (new CMacrosResolverGeneral)->getMacroPositions($str, $types);
1200
1201							foreach (array_reverse($matched_macros, true) as $pos => $macro) {
1202								$macro_value = array_key_exists($macro, $macros_posted)
1203									? $macros_posted[$macro]
1204									: '';
1205
1206								$str = substr_replace($str, $macro_value, $pos, strlen($macro));
1207							}
1208						}
1209
1210						unset($str);
1211					}
1212				}
1213			}
1214			elseif (strstr($inputs[$field], '{') !== false) {
1215				$matched_macros = (new CMacrosResolverGeneral)->getMacroPositions($inputs[$field], $types);
1216
1217				foreach (array_reverse($matched_macros, true) as $pos => $macro) {
1218					$macro_value = array_key_exists($macro, $macros_posted)
1219						? $macros_posted[$macro]
1220						: '';
1221
1222					if ($inputs['type'] == ITEM_TYPE_HTTPAGENT && $field === 'posts') {
1223						if ($inputs['post_type'] == ZBX_POSTTYPE_JSON && !is_numeric($macro_value)) {
1224							$macro_value = json_encode($macro_value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
1225							// Remove " wrapping.
1226							$macro_value = substr($macro_value, 1, -1);
1227						}
1228						elseif ($inputs['post_type'] == ZBX_POSTTYPE_XML) {
1229							$macro_value = htmlentities($macro_value);
1230						}
1231					}
1232
1233					$inputs[$field] = substr_replace($inputs[$field], $macro_value, $pos, strlen($macro));
1234				}
1235			}
1236		}
1237
1238		// Resolve interface details (SNMP) macros separately.
1239		if (array_key_exists('interface', $inputs) && array_key_exists('details', $inputs['interface'])) {
1240			foreach ($inputs['interface']['details'] as &$field) {
1241				if (strstr($field, '{') !== false) {
1242					$matched_macros = (new CMacrosResolverGeneral)->getMacroPositions($field, ['usermacros' => true]);
1243
1244					foreach (array_reverse($matched_macros, true) as $pos => $macro) {
1245						$macro_value = array_key_exists($macro, $macros_posted)
1246							? $macros_posted[$macro]
1247							: '';
1248
1249						$field = substr_replace($field, $macro_value, $pos, strlen($macro));
1250					}
1251				}
1252			}
1253			unset($field);
1254		}
1255
1256		return $inputs;
1257	}
1258
1259	/**
1260	 * Get single input parameter. Converts value fields to specified newline.
1261	 *
1262	 * @return var
1263	 */
1264	public function getInput($var, $default = null) {
1265		$value = parent::getInput($var, $default);
1266		if ($var === 'value' || $var === 'prev_value') {
1267			$value = str_replace("\r\n", "\n", $value);
1268
1269			if ($this->eol == ZBX_EOL_CRLF) {
1270				$value = str_replace("\n", "\r\n", $value);
1271			}
1272		}
1273
1274		return $value;
1275	}
1276
1277
1278	/**
1279	 * Validates interface object in context of current item type.
1280	 *
1281	 * @param array  $interface
1282	 * @param string $interface['address']               (optional)
1283	 * @param string $interface['port']                  (optional)
1284	 * @param array  $interface['details']               (optional)
1285	 * @param int    $interface['details']['version']
1286	 * @param string $interface['details']['community']  (optional)
1287	 *
1288	 * @return bool
1289	 */
1290	final protected function validateInterface(array $interface): bool {
1291		if ($this->item_type == ITEM_TYPE_SNMP) {
1292			if (($interface['details']['version'] == SNMP_V1 || $interface['details']['version'] == SNMP_V2C)
1293					&& (!array_key_exists('community', $interface['details'])
1294						|| $interface['details']['community'] === '')) {
1295				error(_s('Incorrect value for field "%1$s": %2$s.', _('SNMP community'), _('cannot be empty')));
1296
1297				return false;
1298			}
1299		}
1300
1301		if ($this->items_require_interface[$this->item_type]['address']
1302				&& (!array_key_exists('address', $interface) || $interface['address'] === '')) {
1303			error(_s('Incorrect value for field "%1$s": %2$s.', _('Host address'), _('cannot be empty')));
1304
1305			return false;
1306		}
1307
1308		if ($this->items_require_interface[$this->item_type]['port']
1309				&& (!array_key_exists('port', $interface) || $interface['port'] === '')) {
1310			error(_s('Incorrect value for field "%1$s": %2$s.', _('Port'), _('cannot be empty')));
1311
1312			return false;
1313		}
1314
1315		return true;
1316	}
1317}
1318