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
22require_once dirname(__FILE__).'/include/config.inc.php';
23require_once dirname(__FILE__).'/include/hosts.inc.php';
24require_once dirname(__FILE__).'/include/items.inc.php';
25require_once dirname(__FILE__).'/include/forms.inc.php';
26
27$page['title'] = _('Configuration of discovery rules');
28$page['file'] = 'host_discovery.php';
29$page['scripts'] = ['class.cviewswitcher.js', 'multilineinput.js', 'multiselect.js', 'items.js', 'textareaflexible.js',
30	'class.tab-indicators.js'
31];
32
33require_once dirname(__FILE__).'/include/page_header.php';
34
35$paramsFieldName = getParamFieldNameByType(getRequest('type', 0));
36
37// supported eval types
38$evalTypes = [
39	CONDITION_EVAL_TYPE_AND_OR,
40	CONDITION_EVAL_TYPE_AND,
41	CONDITION_EVAL_TYPE_OR,
42	CONDITION_EVAL_TYPE_EXPRESSION
43];
44
45// VAR	TYPE	OPTIONAL	FLAGS	VALIDATION	EXCEPTION
46$fields = [
47	'hostid' =>					[T_ZBX_INT, O_OPT, P_SYS,	DB_ID,		'isset({form}) && !isset({itemid})'],
48	'itemid' =>					[T_ZBX_INT, O_NO,	P_SYS,	DB_ID,		'(isset({form}) && ({form} == "update"))'],
49	'interfaceid' =>			[T_ZBX_INT, O_OPT, P_SYS,	DB_ID, null, _('Interface')],
50	'name' =>					[T_ZBX_STR, O_OPT, null,	NOT_EMPTY, 'isset({add}) || isset({update})', _('Name')],
51	'description' =>			[T_ZBX_STR, O_OPT, null,	null,		'isset({add}) || isset({update})'],
52	'key' =>					[T_ZBX_STR, O_OPT, null,	NOT_EMPTY,	'isset({add}) || isset({update})', _('Key')],
53	'master_itemid' =>			[T_ZBX_STR, O_OPT, null,	null,
54									'(isset({add}) || isset({update})) && isset({type})'.
55										' && {type} == '.ITEM_TYPE_DEPENDENT,
56									_('Master item')
57								],
58	'delay' =>					[T_ZBX_TU, O_OPT, P_ALLOW_USER_MACRO, null,
59									'(isset({add}) || isset({update})) && isset({type})'.
60										' && {type} != '.ITEM_TYPE_TRAPPER.' && {type} != '.ITEM_TYPE_SNMPTRAP.
61										' && {type} != '.ITEM_TYPE_DEPENDENT.
62										' && !({type} == '.ITEM_TYPE_ZABBIX_ACTIVE.
63											' && isset({key}) && strncmp({key}, "mqtt.get", 8) === 0)',
64									_('Update interval')
65								],
66	'delay_flex' =>				[T_ZBX_STR, O_OPT, null,	null,			null],
67	'status' =>					[T_ZBX_INT, O_OPT, null,	IN(ITEM_STATUS_ACTIVE), null],
68	'type' =>					[T_ZBX_INT, O_OPT, null,
69									IN([-1, ITEM_TYPE_ZABBIX, ITEM_TYPE_TRAPPER, ITEM_TYPE_SIMPLE, ITEM_TYPE_INTERNAL,
70										ITEM_TYPE_ZABBIX_ACTIVE, ITEM_TYPE_EXTERNAL, ITEM_TYPE_DB_MONITOR,
71										ITEM_TYPE_IPMI, ITEM_TYPE_SSH, ITEM_TYPE_TELNET, ITEM_TYPE_JMX,
72										ITEM_TYPE_DEPENDENT, ITEM_TYPE_HTTPAGENT, ITEM_TYPE_SNMP, ITEM_TYPE_SCRIPT
73									]),
74									'isset({add}) || isset({update})'
75								],
76	'authtype' =>				[T_ZBX_INT, O_OPT, null,	IN(ITEM_AUTHTYPE_PASSWORD.','.ITEM_AUTHTYPE_PUBLICKEY),
77		'(isset({add}) || isset({update})) && isset({type}) && {type} == '.ITEM_TYPE_SSH],
78	'username' =>				[T_ZBX_STR, O_OPT, null,	NOT_EMPTY,
79									'(isset({add}) || isset({update})) && isset({type})'.
80										' && '.IN(ITEM_TYPE_SSH.','.ITEM_TYPE_TELNET, 'type'),
81									_('User name')
82								],
83	'password' =>				[T_ZBX_STR, O_OPT, null,	null,
84									'(isset({add}) || isset({update})) && isset({type})'.
85										' && '.IN(ITEM_TYPE_SSH.','.ITEM_TYPE_TELNET, 'type')
86								],
87	'publickey' =>				[T_ZBX_STR, O_OPT, null,	null,
88									'(isset({add}) || isset({update})) && isset({type}) && {type} == '.ITEM_TYPE_SSH.
89										' && {authtype} == '.ITEM_AUTHTYPE_PUBLICKEY
90								],
91	'privatekey' =>				[T_ZBX_STR, O_OPT, null,	null,
92									'(isset({add}) || isset({update})) && isset({type}) && {type} == '.ITEM_TYPE_SSH.
93										' && {authtype} == '.ITEM_AUTHTYPE_PUBLICKEY
94								],
95	$paramsFieldName =>			[T_ZBX_STR, O_OPT, null,	NOT_EMPTY,	'(isset({add}) || isset({update}))'.
96									' && isset({type}) && '.IN(ITEM_TYPE_SSH.','.ITEM_TYPE_DB_MONITOR.','.
97										ITEM_TYPE_TELNET.','.ITEM_TYPE_CALCULATED.','.ITEM_TYPE_SCRIPT, 'type'
98									),
99									getParamFieldLabelByType(getRequest('type', 0))
100								],
101	'snmp_oid' =>				[T_ZBX_STR, O_OPT, null,	NOT_EMPTY,
102									'(isset({add}) || isset({update})) && isset({type})'.
103										' && {type} == '.ITEM_TYPE_SNMP,
104									_('SNMP OID')
105								],
106	'ipmi_sensor' =>			[T_ZBX_STR, O_OPT, P_NO_TRIM, null,
107									'(isset({add}) || isset({update})) && isset({type}) && {type} == '.ITEM_TYPE_IPMI,
108									_('IPMI sensor')
109								],
110	'trapper_hosts' =>			[T_ZBX_STR, O_OPT, null,	null,
111									'(isset({add}) || isset({update})) && isset({type}) && {type} == 2'
112								],
113	'lifetime' =>				[T_ZBX_STR, O_OPT, null,	null,		'isset({add}) || isset({update})'],
114	'evaltype' =>				[T_ZBX_INT, O_OPT, null, 	IN($evalTypes), 'isset({add}) || isset({update})'],
115	'formula' =>				[T_ZBX_STR, O_OPT, null,	null,		'isset({add}) || isset({update})'],
116	'conditions' =>				[T_ZBX_STR, O_OPT, P_SYS,	null,		null],
117	'lld_macro_paths' =>		[T_ZBX_STR, O_OPT, P_SYS,	null,		null],
118	'jmx_endpoint' =>			[T_ZBX_STR, O_OPT, null,	NOT_EMPTY,
119		'(isset({add}) || isset({update})) && isset({type}) && {type} == '.ITEM_TYPE_JMX
120	],
121	'timeout' => 				[T_ZBX_TU, O_OPT, P_ALLOW_USER_MACRO,	null,
122									'(isset({add}) || isset({update})) && isset({type})'.
123										' && '.IN(ITEM_TYPE_HTTPAGENT.','.ITEM_TYPE_SCRIPT, 'type'),
124									_('Timeout')
125								],
126	'url' =>					[T_ZBX_STR, O_OPT, null,	NOT_EMPTY,
127									'(isset({add}) || isset({update})) && isset({type})'.
128										' && {type} == '.ITEM_TYPE_HTTPAGENT,
129									_('URL')
130								],
131	'query_fields' =>			[T_ZBX_STR, O_OPT, null,	null,		null],
132	'parameters' =>				[T_ZBX_STR, O_OPT, null,	null,		null],
133	'posts' =>					[T_ZBX_STR, O_OPT, null,	null,		null],
134	'status_codes' =>			[T_ZBX_STR, O_OPT, null,	null,		null],
135	'follow_redirects' =>		[T_ZBX_INT, O_OPT, null,
136									IN([HTTPTEST_STEP_FOLLOW_REDIRECTS_OFF, HTTPTEST_STEP_FOLLOW_REDIRECTS_ON]),
137									null
138								],
139	'post_type' =>				[T_ZBX_INT, O_OPT, null,
140									IN([ZBX_POSTTYPE_RAW, ZBX_POSTTYPE_JSON, ZBX_POSTTYPE_XML]),
141									null
142								],
143	'http_proxy' =>				[T_ZBX_STR, O_OPT, null,	null,		null],
144	'headers' => 				[T_ZBX_STR, O_OPT, null,	null,		null],
145	'retrieve_mode' =>			[T_ZBX_INT, O_OPT, null,
146									IN([HTTPTEST_STEP_RETRIEVE_MODE_CONTENT, HTTPTEST_STEP_RETRIEVE_MODE_HEADERS,
147										HTTPTEST_STEP_RETRIEVE_MODE_BOTH
148									]),
149									null
150								],
151	'request_method' =>			[T_ZBX_INT, O_OPT, null,
152									IN([HTTPCHECK_REQUEST_GET, HTTPCHECK_REQUEST_POST, HTTPCHECK_REQUEST_PUT,
153										HTTPCHECK_REQUEST_HEAD
154									]),
155									null
156								],
157	'allow_traps' =>			[T_ZBX_INT, O_OPT, null,	IN([HTTPCHECK_ALLOW_TRAPS_OFF, HTTPCHECK_ALLOW_TRAPS_ON]),
158									null
159								],
160	'ssl_cert_file' =>			[T_ZBX_STR, O_OPT, null,	null,		null],
161	'ssl_key_file' =>			[T_ZBX_STR, O_OPT, null,	null,		null],
162	'ssl_key_password' =>		[T_ZBX_STR, O_OPT, null,	null,		null],
163	'verify_peer' =>			[T_ZBX_INT, O_OPT, null,
164									IN([HTTPTEST_VERIFY_PEER_OFF, HTTPTEST_VERIFY_PEER_ON]),
165									null
166								],
167	'verify_host' =>			[T_ZBX_INT, O_OPT, null,
168									IN([HTTPTEST_VERIFY_HOST_OFF, HTTPTEST_VERIFY_HOST_ON]),
169									null
170								],
171	'http_authtype' =>			[T_ZBX_INT, O_OPT, null,
172									IN([HTTPTEST_AUTH_NONE, HTTPTEST_AUTH_BASIC, HTTPTEST_AUTH_NTLM,
173										HTTPTEST_AUTH_KERBEROS, HTTPTEST_AUTH_DIGEST
174									]),
175									null
176								],
177	'http_username' =>			[T_ZBX_STR, O_OPT, null,	null,
178									'(isset({add}) || isset({update})) && isset({http_authtype})'.
179										' && ({http_authtype} == '.HTTPTEST_AUTH_BASIC.
180											' || {http_authtype} == '.HTTPTEST_AUTH_NTLM.
181											' || {http_authtype} == '.HTTPTEST_AUTH_KERBEROS.
182											' || {http_authtype} == '.HTTPTEST_AUTH_DIGEST.
183										')',
184									_('Username')
185								],
186	'http_password' =>			[T_ZBX_STR, O_OPT, null,	null,
187									'(isset({add}) || isset({update})) && isset({http_authtype})'.
188										' && ({http_authtype} == '.HTTPTEST_AUTH_BASIC.
189											' || {http_authtype} == '.HTTPTEST_AUTH_NTLM.
190											' || {http_authtype} == '.HTTPTEST_AUTH_KERBEROS.
191											' || {http_authtype} == '.HTTPTEST_AUTH_DIGEST.
192										')',
193									_('Password')
194								],
195	'preprocessing' =>			[T_ZBX_STR, O_OPT, P_NO_TRIM,	null,	null],
196	'overrides' =>				[T_ZBX_STR, O_OPT, P_NO_TRIM,	null,	null],
197	'context' =>				[T_ZBX_STR, O_MAND, P_SYS,		IN('"host", "template"'),	null],
198	// actions
199	'action' =>					[T_ZBX_STR, O_OPT, P_SYS|P_ACT,
200									IN('"discoveryrule.massdelete","discoveryrule.massdisable",'.
201										'"discoveryrule.massenable","discoveryrule.masscheck_now"'
202									),
203									null
204								],
205	'g_hostdruleid' =>			[T_ZBX_INT, O_OPT, null,	DB_ID,		null],
206	'add' =>					[T_ZBX_STR, O_OPT, P_SYS|P_ACT, null,	null],
207	'update' =>					[T_ZBX_STR, O_OPT, P_SYS|P_ACT, null,	null],
208	'clone' =>					[T_ZBX_STR, O_OPT, P_SYS|P_ACT, null,	null],
209	'delete' =>					[T_ZBX_STR, O_OPT, P_SYS|P_ACT, null,	null],
210	'cancel' =>					[T_ZBX_STR, O_OPT, P_SYS,	null,		null],
211	'check_now' =>				[T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
212	'form' =>					[T_ZBX_STR, O_OPT, P_SYS,	null,		null],
213	'form_refresh' =>			[T_ZBX_INT, O_OPT, null,	null,		null],
214	// filter
215	'filter_set' =>				[T_ZBX_STR, O_OPT, null,	null,		null],
216	'filter_rst' =>				[T_ZBX_STR, O_OPT, null,	null,		null],
217	'filter_groupids' =>		[T_ZBX_INT, O_OPT, null,	DB_ID,		null],
218	'filter_hostids' =>			[T_ZBX_INT, O_OPT, null,	DB_ID,		null],
219	'filter_name' =>			[T_ZBX_STR, O_OPT, null,	null,		null],
220	'filter_key' =>				[T_ZBX_STR, O_OPT, null,	null,		null],
221	'filter_type' =>			[T_ZBX_INT, O_OPT, null,
222									IN([-1, ITEM_TYPE_ZABBIX, ITEM_TYPE_TRAPPER, ITEM_TYPE_SIMPLE, ITEM_TYPE_INTERNAL,
223										ITEM_TYPE_ZABBIX_ACTIVE, ITEM_TYPE_EXTERNAL, ITEM_TYPE_DB_MONITOR,
224										ITEM_TYPE_IPMI, ITEM_TYPE_SSH, ITEM_TYPE_TELNET, ITEM_TYPE_JMX,
225										ITEM_TYPE_DEPENDENT, ITEM_TYPE_HTTPAGENT, ITEM_TYPE_SNMP, ITEM_TYPE_SCRIPT
226									]),
227									null
228								],
229	'filter_delay' =>			[T_ZBX_STR, O_OPT, P_UNSET_EMPTY, null, null, _('Update interval')],
230	'filter_lifetime' =>		[T_ZBX_STR, O_OPT, null,	null,		null],
231	'filter_snmp_oid' =>		[T_ZBX_STR, O_OPT, null,	null,		null],
232	'filter_state' =>			[T_ZBX_INT, O_OPT, null,	IN([-1, ITEM_STATE_NORMAL, ITEM_STATE_NOTSUPPORTED]),
233									null
234								],
235	'filter_status' =>			[T_ZBX_INT, O_OPT, null,	IN([-1, ITEM_STATUS_ACTIVE, ITEM_STATUS_DISABLED]),
236									null
237								],
238	// sort and sortorder
239	'sort' =>					[T_ZBX_STR, O_OPT, P_SYS, IN('"delay","key_","name","status","type"'),	null],
240	'sortorder' =>				[T_ZBX_STR, O_OPT, P_SYS, IN('"'.ZBX_SORT_DOWN.'","'.ZBX_SORT_UP.'"'),	null]
241];
242check_fields($fields);
243
244$_REQUEST['params'] = getRequest($paramsFieldName, '');
245unset($_REQUEST[$paramsFieldName]);
246$item = [];
247
248/*
249 * Permissions
250 */
251$hostid = getRequest('hostid', 0);
252
253if (getRequest('itemid', false)) {
254	$item = API::DiscoveryRule()->get([
255		'itemids' => getRequest('itemid'),
256		'output' => API_OUTPUT_EXTEND,
257		'selectHosts' => ['hostid', 'name', 'status', 'flags'],
258		'selectFilter' => ['formula', 'evaltype', 'conditions'],
259		'selectLLDMacroPaths' => ['lld_macro', 'path'],
260		'selectPreprocessing' => ['type', 'params', 'error_handler', 'error_handler_params'],
261		'selectOverrides' => ['name', 'step', 'stop', 'filter', 'operations'],
262		'editable' => true
263	]);
264	$item = reset($item);
265	if (!$item) {
266		access_deny();
267	}
268	$_REQUEST['hostid'] = $item['hostid'];
269	$host = reset($item['hosts']);
270
271	foreach ($item['overrides'] as &$override) {
272		if (!array_key_exists('operations', $override)) {
273			continue;
274		}
275
276		foreach ($override['operations'] as &$operation) {
277			if (array_key_exists('optag', $operation)) {
278				CArrayHelper::sort($operation['optag'], ['tag', 'value']);
279				$operation['optag'] = array_values($operation['optag']);
280			}
281		}
282		unset($operation);
283	}
284	unset($override);
285}
286elseif ($hostid) {
287	$hosts = API::Host()->get([
288		'output' => ['hostid', 'name', 'status'],
289		'hostids' => $hostid,
290		'templated_hosts' => true,
291		'editable' => true
292	]);
293	$host = reset($hosts);
294	if (!$host) {
295		access_deny();
296	}
297}
298
299$prefix = (getRequest('context') === 'host') ? 'web.hosts.' : 'web.templates.';
300
301/**
302 * Filter.
303 */
304$sort_field = getRequest('sort', CProfile::get($prefix.$page['file'].'.sort', 'name'));
305$sort_order = getRequest('sortorder', CProfile::get($prefix.$page['file'].'.sortorder', ZBX_SORT_UP));
306
307CProfile::update($prefix.$page['file'].'.sort', $sort_field, PROFILE_TYPE_STR);
308CProfile::update($prefix.$page['file'].'.sortorder', $sort_order, PROFILE_TYPE_STR);
309
310if (hasRequest('filter_set')) {
311	CProfile::updateArray($prefix.'host_discovery.filter.groupids', getRequest('filter_groupids', []), PROFILE_TYPE_ID);
312	CProfile::updateArray($prefix.'host_discovery.filter.hostids', getRequest('filter_hostids', []), PROFILE_TYPE_ID);
313	CProfile::update($prefix.'host_discovery.filter.name', getRequest('filter_name', ''), PROFILE_TYPE_STR);
314	CProfile::update($prefix.'host_discovery.filter.key', getRequest('filter_key', ''), PROFILE_TYPE_STR);
315	CProfile::update($prefix.'host_discovery.filter.type', getRequest('filter_type', -1), PROFILE_TYPE_INT);
316	CProfile::update($prefix.'host_discovery.filter.delay', getRequest('filter_delay', ''), PROFILE_TYPE_STR);
317	CProfile::update($prefix.'host_discovery.filter.lifetime', getRequest('filter_lifetime', ''), PROFILE_TYPE_STR);
318	CProfile::update($prefix.'host_discovery.filter.snmp_oid', getRequest('filter_snmp_oid', ''), PROFILE_TYPE_STR);
319	CProfile::update($prefix.'host_discovery.filter.state', getRequest('filter_state', -1), PROFILE_TYPE_INT);
320	CProfile::update($prefix.'host_discovery.filter.status', getRequest('filter_status', -1), PROFILE_TYPE_INT);
321}
322elseif (hasRequest('filter_rst')) {
323	CProfile::deleteIdx($prefix.'host_discovery.filter.groupids');
324
325	if (count(CProfile::getArray($prefix.'host_discovery.filter.hostids', [])) != 1) {
326		CProfile::deleteIdx($prefix.'host_discovery.filter.hostids');
327	}
328
329	CProfile::delete($prefix.'host_discovery.filter.name');
330	CProfile::delete($prefix.'host_discovery.filter.key');
331	CProfile::delete($prefix.'host_discovery.filter.type');
332	CProfile::delete($prefix.'host_discovery.filter.delay');
333	CProfile::delete($prefix.'host_discovery.filter.lifetime');
334	CProfile::delete($prefix.'host_discovery.filter.snmp_oid');
335	CProfile::delete($prefix.'host_discovery.filter.state');
336	CProfile::delete($prefix.'host_discovery.filter.status');
337}
338
339$filter = [
340	'groups' => CProfile::getArray($prefix.'host_discovery.filter.groupids', []),
341	'hosts' => CProfile::getArray($prefix.'host_discovery.filter.hostids', []),
342	'name' => CProfile::get($prefix.'host_discovery.filter.name', ''),
343	'key' => CProfile::get($prefix.'host_discovery.filter.key', ''),
344	'type' => CProfile::get($prefix.'host_discovery.filter.type', -1),
345	'delay' => CProfile::get($prefix.'host_discovery.filter.delay', ''),
346	'lifetime' => CProfile::get($prefix.'host_discovery.filter.lifetime', ''),
347	'snmp_oid' => CProfile::get($prefix.'host_discovery.filter.snmp_oid', ''),
348	'state' => CProfile::get($prefix.'host_discovery.filter.state', -1),
349	'status' => CProfile::get($prefix.'host_discovery.filter.status', -1)
350];
351
352$filter_groupids = [];
353$filter_hostids = [];
354
355// Get host groups.
356if ($filter['groups']) {
357	$filter['groups'] = CArrayHelper::renameObjectsKeys(API::HostGroup()->get([
358		'output' => ['groupid', 'name'],
359		'groupids' => $filter['groups'],
360		'editable' => true,
361		'preservekeys' => true
362	]), ['groupid' => 'id']);
363
364	$filter_groupids = getSubGroups(array_keys($filter['groups']));
365}
366
367// Get hosts.
368if ($filter['hosts']) {
369	if (getRequest('context') === 'host') {
370		$filter['hosts'] = CArrayHelper::renameObjectsKeys(API::Host()->get([
371			'output' => ['hostid', 'name'],
372			'hostids' => $filter['hosts'],
373			'editable' => true,
374			'preservekeys' => true
375		]), ['hostid' => 'id']);
376	}
377	else {
378		$filter['hosts'] = CArrayHelper::renameObjectsKeys(API::Template()->get([
379			'output' => ['templateid', 'name'],
380			'templateids' => $filter['hosts'],
381			'editable' => true,
382			'preservekeys' => true
383		]), ['templateid' => 'id']);
384	}
385
386	$filter_hostids = array_keys($filter['hosts']);
387
388	sort($filter_hostids);
389}
390
391$checkbox_hash = crc32(implode('', $filter_hostids));
392
393// Convert CR+LF to LF in preprocessing script.
394if (hasRequest('preprocessing')) {
395	foreach ($_REQUEST['preprocessing'] as &$step) {
396		if ($step['type'] == ZBX_PREPROC_SCRIPT) {
397			$step['params'][0] = CRLFtoLF($step['params'][0]);
398		}
399	}
400	unset($step);
401}
402
403/*
404 * Actions
405 */
406if (hasRequest('delete') && hasRequest('itemid')) {
407	$result = API::DiscoveryRule()->delete([getRequest('itemid')]);
408
409	if ($result) {
410		uncheckTableRows($checkbox_hash);
411	}
412	show_messages($result, _('Discovery rule deleted'), _('Cannot delete discovery rule'));
413
414	unset($_REQUEST['itemid'], $_REQUEST['form']);
415}
416elseif (hasRequest('check_now') && hasRequest('itemid')) {
417	$result = (bool) API::Task()->create([
418		'type' => ZBX_TM_DATA_TYPE_CHECK_NOW,
419		'request' => [
420			'itemid' => getRequest('itemid')
421		]
422	]);
423
424	show_messages($result, _('Request sent successfully'), _('Cannot send request'));
425}
426elseif (hasRequest('add') || hasRequest('update')) {
427	$result = true;
428
429	$delay = getRequest('delay', DB::getDefault('items', 'delay'));
430	$type = getRequest('type', ITEM_TYPE_ZABBIX);
431
432	/*
433	 * "delay_flex" is a temporary field that collects flexible and scheduling intervals separated by a semicolon.
434	 * In the end, custom intervals together with "delay" are stored in the "delay" variable.
435	 */
436	if ($type != ITEM_TYPE_TRAPPER && $type != ITEM_TYPE_SNMPTRAP
437			&& ($type != ITEM_TYPE_ZABBIX_ACTIVE || strncmp(getRequest('key'), 'mqtt.get', 8) !== 0)
438			&& hasRequest('delay_flex')) {
439		$intervals = [];
440		$simple_interval_parser = new CSimpleIntervalParser(['usermacros' => true]);
441		$time_period_parser = new CTimePeriodParser(['usermacros' => true]);
442		$scheduling_interval_parser = new CSchedulingIntervalParser(['usermacros' => true]);
443
444		foreach (getRequest('delay_flex') as $interval) {
445			if ($interval['type'] == ITEM_DELAY_FLEXIBLE) {
446				if ($interval['delay'] === '' && $interval['period'] === '') {
447					continue;
448				}
449
450				if ($simple_interval_parser->parse($interval['delay']) != CParser::PARSE_SUCCESS) {
451					$result = false;
452					info(_s('Invalid interval "%1$s".', $interval['delay']));
453					break;
454				}
455
456				if ($time_period_parser->parse($interval['period']) != CParser::PARSE_SUCCESS) {
457					$result = false;
458					info(_s('Invalid interval "%1$s".', $interval['period']));
459					break;
460				}
461
462				$intervals[] = $interval['delay'].'/'.$interval['period'];
463			}
464			else {
465				if ($interval['schedule'] === '') {
466					continue;
467				}
468
469				if ($scheduling_interval_parser->parse($interval['schedule']) != CParser::PARSE_SUCCESS) {
470					$result = false;
471					info(_s('Invalid interval "%1$s".', $interval['schedule']));
472					break;
473				}
474
475				$intervals[] = $interval['schedule'];
476			}
477		}
478
479		if ($intervals) {
480			$delay .= ';'.implode(';', $intervals);
481		}
482	}
483
484	if ($result) {
485		$preprocessing = getRequest('preprocessing', []);
486
487		foreach ($preprocessing as &$step) {
488			switch ($step['type']) {
489				case ZBX_PREPROC_PROMETHEUS_TO_JSON:
490					$step['params'] = trim($step['params'][0]);
491					break;
492
493				case ZBX_PREPROC_XPATH:
494				case ZBX_PREPROC_JSONPATH:
495				case ZBX_PREPROC_VALIDATE_NOT_REGEX:
496				case ZBX_PREPROC_ERROR_FIELD_JSON:
497				case ZBX_PREPROC_ERROR_FIELD_XML:
498				case ZBX_PREPROC_THROTTLE_TIMED_VALUE:
499				case ZBX_PREPROC_SCRIPT:
500					$step['params'] = $step['params'][0];
501					break;
502
503				case ZBX_PREPROC_REGSUB:
504				case ZBX_PREPROC_STR_REPLACE:
505					$step['params'] = implode("\n", $step['params']);
506					break;
507
508				// ZBX-16642
509				case ZBX_PREPROC_CSV_TO_JSON:
510					if (!array_key_exists(2, $step['params'])) {
511						$step['params'][2] = ZBX_PREPROC_CSV_NO_HEADER;
512					}
513					$step['params'] = implode("\n", $step['params']);
514					break;
515
516				default:
517					$step['params'] = '';
518			}
519
520			$step += [
521				'error_handler' => ZBX_PREPROC_FAIL_DEFAULT,
522				'error_handler_params' => ''
523			];
524		}
525		unset($step);
526
527		$newItem = [
528			'itemid' => getRequest('itemid'),
529			'interfaceid' => getRequest('interfaceid'),
530			'name' => getRequest('name'),
531			'description' => getRequest('description'),
532			'key_' => getRequest('key'),
533			'hostid' => getRequest('hostid'),
534			'delay' => $delay,
535			'status' => getRequest('status', ITEM_STATUS_DISABLED),
536			'type' => getRequest('type'),
537			'snmp_oid' => getRequest('snmp_oid'),
538			'trapper_hosts' => getRequest('trapper_hosts'),
539			'authtype' => getRequest('authtype'),
540			'username' => getRequest('username'),
541			'password' => getRequest('password'),
542			'publickey' => getRequest('publickey'),
543			'privatekey' => getRequest('privatekey'),
544			'params' => getRequest('params'),
545			'ipmi_sensor' => getRequest('ipmi_sensor'),
546			'lifetime' => getRequest('lifetime')
547		];
548
549		if ($newItem['type'] == ITEM_TYPE_HTTPAGENT) {
550			$http_item = [
551				'timeout' => getRequest('timeout', DB::getDefault('items', 'timeout')),
552				'url' => getRequest('url'),
553				'query_fields' => getRequest('query_fields', []),
554				'posts' => getRequest('posts'),
555				'status_codes' => getRequest('status_codes', DB::getDefault('items', 'status_codes')),
556				'follow_redirects' => (int) getRequest('follow_redirects'),
557				'post_type' => (int) getRequest('post_type'),
558				'http_proxy' => getRequest('http_proxy'),
559				'headers' => getRequest('headers', []),
560				'retrieve_mode' => (int) getRequest('retrieve_mode'),
561				'request_method' => (int) getRequest('request_method'),
562				'output_format' => (int) getRequest('output_format'),
563				'allow_traps' => (int) getRequest('allow_traps', HTTPCHECK_ALLOW_TRAPS_OFF),
564				'ssl_cert_file' => getRequest('ssl_cert_file'),
565				'ssl_key_file' => getRequest('ssl_key_file'),
566				'ssl_key_password' => getRequest('ssl_key_password'),
567				'verify_peer' => (int) getRequest('verify_peer'),
568				'verify_host' => (int) getRequest('verify_host'),
569				'authtype' => getRequest('http_authtype', HTTPTEST_AUTH_NONE),
570				'username' => getRequest('http_username', ''),
571				'password' => getRequest('http_password', '')
572			];
573			$newItem = prepareItemHttpAgentFormData($http_item) + $newItem;
574		}
575
576		if ($newItem['type'] == ITEM_TYPE_SCRIPT) {
577			$script_item = [
578				'parameters' => getRequest('parameters', []),
579				'timeout' => getRequest('timeout', DB::getDefault('items', 'timeout'))
580			];
581
582			$newItem = prepareScriptItemFormData($script_item) + $newItem;
583		}
584
585		if ($newItem['type'] == ITEM_TYPE_JMX) {
586			$newItem['jmx_endpoint'] = getRequest('jmx_endpoint', '');
587		}
588
589		if (getRequest('type') == ITEM_TYPE_DEPENDENT) {
590			$newItem['master_itemid'] = getRequest('master_itemid');
591		}
592
593		// add macros; ignore empty new macros
594		$lld_rule_filter = [
595			'evaltype' => getRequest('evaltype'),
596			'conditions' => []
597		];
598		$conditions = getRequest('conditions', []);
599		ksort($conditions);
600		$conditions = array_values($conditions);
601		foreach ($conditions as $condition) {
602			if (!zbx_empty($condition['macro'])) {
603				$condition['macro'] = mb_strtoupper($condition['macro']);
604
605				$lld_rule_filter['conditions'][] = $condition;
606			}
607		}
608		if ($lld_rule_filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
609			// if only one or no conditions are left, reset the evaltype to and/or and clear the formula
610			if (count($lld_rule_filter['conditions']) <= 1) {
611				$lld_rule_filter['formula'] = '';
612				$lld_rule_filter['evaltype'] = CONDITION_EVAL_TYPE_AND_OR;
613			}
614			else {
615				$lld_rule_filter['formula'] = getRequest('formula');
616			}
617		}
618		$newItem['filter'] = $lld_rule_filter;
619
620		$lld_macro_paths = getRequest('lld_macro_paths', []);
621
622		foreach ($lld_macro_paths as &$lld_macro_path) {
623			$lld_macro_path['lld_macro'] = mb_strtoupper($lld_macro_path['lld_macro']);
624		}
625		unset($lld_macro_path);
626
627		$newItem['lld_macro_paths'] = $lld_macro_paths;
628
629		foreach ($newItem['lld_macro_paths'] as $i => $lld_macro_path) {
630			if ($lld_macro_path['lld_macro'] === '' && $lld_macro_path['path'] === '') {
631				unset($newItem['lld_macro_paths'][$i]);
632			}
633		}
634
635		$overrides = getRequest('overrides', []);
636		$newItem['overrides'] = $overrides;
637
638		if (hasRequest('update')) {
639			DBstart();
640
641			// Unset equal values if item script type and parameters have not changed.
642			$compare = function($arr, $arr2) {
643				return (array_combine(array_column($arr, 'name'), array_column($arr, 'value')) ==
644					array_combine(array_column($arr2, 'name'), array_column($arr2, 'value'))
645				);
646			};
647			if ($newItem['type'] == ITEM_TYPE_SCRIPT && $newItem['type'] == $item['type']
648					&& $compare($item['parameters'], $newItem['parameters'])) {
649				unset($newItem['parameters']);
650			}
651
652			if ($newItem['type'] == $item['type']) {
653				$newItem = CArrayHelper::unsetEqualValues($newItem, $item, ['itemid']);
654			}
655
656			// don't update the filter if it hasn't changed
657			$conditionsChanged = false;
658			if (count($newItem['filter']['conditions']) != count($item['filter']['conditions'])) {
659				$conditionsChanged = true;
660			}
661			else {
662				$conditions = $item['filter']['conditions'];
663				foreach ($newItem['filter']['conditions'] as $i => $condition) {
664					if (CArrayHelper::unsetEqualValues($condition, $conditions[$i])) {
665						$conditionsChanged = true;
666						break;
667					}
668				}
669			}
670			$lld_rule_filter = CArrayHelper::unsetEqualValues($newItem['filter'], $item['filter']);
671			if (!isset($lld_rule_filter['evaltype']) && !isset($lld_rule_filter['formula']) && !$conditionsChanged) {
672				unset($newItem['filter']);
673			}
674
675			$lld_macro_paths_changed = false;
676
677			if (count($newItem['lld_macro_paths']) != count($item['lld_macro_paths'])) {
678				$lld_macro_paths_changed = true;
679			}
680			else {
681				$lld_macro_paths = array_values($item['lld_macro_paths']);
682				$newItem['lld_macro_paths'] = array_values($newItem['lld_macro_paths']);
683
684				foreach ($newItem['lld_macro_paths'] as $i => $lld_macro_path) {
685					if (CArrayHelper::unsetEqualValues($lld_macro_path, $lld_macro_paths[$i])) {
686						$lld_macro_paths_changed = true;
687						break;
688					}
689				}
690			}
691
692			if (!$lld_macro_paths_changed) {
693				unset($newItem['lld_macro_paths']);
694			}
695
696			if ($item['preprocessing'] !== $preprocessing) {
697				$newItem['preprocessing'] = $preprocessing;
698			}
699
700			$result = API::DiscoveryRule()->update($newItem);
701			$result = DBend($result);
702		}
703		else {
704			if (!$newItem['lld_macro_paths']) {
705				unset($newItem['lld_macro_paths']);
706			}
707
708			if ($preprocessing) {
709				$newItem['preprocessing'] = $preprocessing;
710			}
711
712			$result = API::DiscoveryRule()->create([$newItem]);
713		}
714	}
715
716	if (hasRequest('add')) {
717		show_messages($result, _('Discovery rule created'), _('Cannot add discovery rule'));
718	}
719	else {
720		show_messages($result, _('Discovery rule updated'), _('Cannot update discovery rule'));
721	}
722
723	if ($result) {
724		unset($_REQUEST['itemid'], $_REQUEST['form']);
725		uncheckTableRows($checkbox_hash);
726	}
727}
728elseif (hasRequest('action') && str_in_array(getRequest('action'), ['discoveryrule.massenable', 'discoveryrule.massdisable']) && hasRequest('g_hostdruleid')) {
729	$itemids = getRequest('g_hostdruleid');
730	$status = (getRequest('action') === 'discoveryrule.massenable') ? ITEM_STATUS_ACTIVE : ITEM_STATUS_DISABLED;
731
732	$lld_rules = [];
733	foreach ($itemids as $itemid) {
734		$lld_rules[] = ['itemid' => $itemid, 'status' => $status];
735	}
736
737	$result = (bool) API::DiscoveryRule()->update($lld_rules);
738
739	if ($result) {
740		uncheckTableRows($checkbox_hash);
741	}
742
743	$updated = count($itemids);
744
745	$messageSuccess = ($status == ITEM_STATUS_ACTIVE)
746		? _n('Discovery rule enabled', 'Discovery rules enabled', $updated)
747		: _n('Discovery rule disabled', 'Discovery rules disabled', $updated);
748	$messageFailed = ($status == ITEM_STATUS_ACTIVE)
749		? _n('Cannot enable discovery rule', 'Cannot enable discovery rules', $updated)
750		: _n('Cannot disable discovery rule', 'Cannot disable discovery rules', $updated);
751
752	show_messages($result, $messageSuccess, $messageFailed);
753}
754elseif (hasRequest('action') && getRequest('action') === 'discoveryrule.massdelete' && hasRequest('g_hostdruleid')) {
755	$result = API::DiscoveryRule()->delete(getRequest('g_hostdruleid'));
756
757	if ($result) {
758		uncheckTableRows($checkbox_hash);
759	}
760	show_messages($result, _('Discovery rules deleted'), _('Cannot delete discovery rules'));
761}
762elseif (hasRequest('action') && getRequest('action') === 'discoveryrule.masscheck_now' && hasRequest('g_hostdruleid')) {
763	$tasks = [];
764
765	foreach (getRequest('g_hostdruleid') as $taskid) {
766		$tasks[] = [
767			'type' => ZBX_TM_DATA_TYPE_CHECK_NOW,
768			'request' => [
769				'itemid' => $taskid
770			]
771		];
772	}
773
774	$result = (bool) API::Task()->create($tasks);
775
776	if ($result) {
777		uncheckTableRows($checkbox_hash);
778	}
779
780	show_messages($result, _('Request sent successfully'), _('Cannot send request'));
781}
782
783if (hasRequest('action') && hasRequest('g_hostdruleid') && !$result) {
784	$hostdrules = API::DiscoveryRule()->get([
785		'output' => [],
786		'itemids' => getRequest('g_hostdruleid'),
787		'editable' => true
788	]);
789	uncheckTableRows($checkbox_hash, zbx_objectValues($hostdrules, 'itemid'));
790}
791
792/*
793 * Display
794 */
795if (hasRequest('form')) {
796	$has_errors = false;
797	$form_item = (hasRequest('itemid') && !hasRequest('clone')) ? $item : [];
798	$master_itemid = $form_item && !hasRequest('form_refresh')
799		? $form_item['master_itemid']
800		: getRequest('master_itemid');
801
802	if (getRequest('type', $form_item ? $form_item['type'] : null) == ITEM_TYPE_DEPENDENT && $master_itemid != 0) {
803		$db_master_items = API::Item()->get([
804			'output' => ['itemid', 'type', 'hostid', 'name', 'key_'],
805			'itemids' => $master_itemid,
806			'webitems' => true
807		]);
808
809		if (!$db_master_items) {
810			show_messages(false, '', _('No permissions to referred object or it does not exist!'));
811			$has_errors = true;
812		}
813		else {
814			$form_item['master_item'] = $db_master_items[0];
815		}
816	}
817
818	$data = getItemFormData($form_item, ['form' => getRequest('form'), 'is_discovery_rule' => true]);
819	$data['lifetime'] = getRequest('lifetime', DB::getDefault('items', 'lifetime'));
820	$data['evaltype'] = getRequest('evaltype');
821	$data['formula'] = getRequest('formula');
822	$data['conditions'] = getRequest('conditions', []);
823	$data['lld_macro_paths'] = getRequest('lld_macro_paths', []);
824	$data['overrides'] = getRequest('overrides', []);
825	$data['host'] = $host;
826	$data['preprocessing_test_type'] = CControllerPopupItemTestEdit::ZBX_TEST_TYPE_LLD;
827	$data['preprocessing_types'] = CDiscoveryRule::SUPPORTED_PREPROCESSING_TYPES;
828	$data['display_interfaces'] = ($host['status'] == HOST_STATUS_MONITORED
829		|| $host['status'] == HOST_STATUS_NOT_MONITORED
830	);
831
832	if (!hasRequest('form_refresh')) {
833		foreach ($data['preprocessing'] as &$step) {
834			if ($step['type'] == ZBX_PREPROC_SCRIPT) {
835				$step['params'] = [$step['params'], ''];
836			}
837			else {
838				$step['params'] = explode("\n", $step['params']);
839			}
840		}
841		unset($step);
842	}
843
844	// update form
845	if (hasRequest('itemid') && !getRequest('form_refresh')) {
846		$data['lifetime'] = $item['lifetime'];
847		$data['evaltype'] = $item['filter']['evaltype'];
848		$data['formula'] = $item['filter']['formula'];
849		$data['conditions'] = $item['filter']['conditions'];
850		$data['lld_macro_paths'] = $item['lld_macro_paths'];
851		$data['overrides'] = $item['overrides'];
852		// Sort overrides to be listed in step order.
853		CArrayHelper::sort($data['overrides'], ['step']);
854	}
855	// clone form
856	elseif (hasRequest('clone')) {
857		unset($data['itemid']);
858		$data['form'] = 'clone';
859	}
860
861	if ($data['type'] != ITEM_TYPE_JMX) {
862		$data['jmx_endpoint'] = ZBX_DEFAULT_JMX_ENDPOINT;
863	}
864
865	// render view
866	if (!$has_errors) {
867		echo (new CView('configuration.host.discovery.edit', $data))->getOutput();
868	}
869}
870else {
871	$data = [
872		'filter' => $filter,
873		'hostid' => (count($filter_hostids) == 1) ? reset($filter_hostids) : 0,
874		'sort' => $sort_field,
875		'sortorder' => $sort_order,
876		'profileIdx' => $prefix.'host_discovery.filter',
877		'active_tab' => CProfile::get($prefix.'host_discovery.filter.active', 1),
878		'checkbox_hash' => $checkbox_hash,
879		'is_template' => true,
880		'context' => getRequest('context')
881	];
882
883	// Select LLD rules.
884	$options = [
885		'output' => API_OUTPUT_EXTEND,
886		'selectHosts' => ['hostid', 'name', 'status', 'flags'],
887		'selectItems' => API_OUTPUT_COUNT,
888		'selectGraphs' => API_OUTPUT_COUNT,
889		'selectTriggers' => API_OUTPUT_COUNT,
890		'selectHostPrototypes' => API_OUTPUT_COUNT,
891		'editable' => true,
892		'templated' => ($data['context'] === 'template'),
893		'filter' => [],
894		'search' => [],
895		'sortfield' => $sort_field,
896		'limit' => CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT) + 1
897	];
898
899	if ($filter_groupids) {
900		$options['groupids'] = $filter_groupids;
901	}
902
903	if ($filter_hostids) {
904		$options['hostids'] = $filter_hostids;
905	}
906
907	if ($filter['name'] !== '') {
908		$options['search']['name'] = $filter['name'];
909	}
910
911	if ($filter['key'] !== '') {
912		$options['search']['key_'] = $filter['key'];
913	}
914
915	if ($filter['type'] != -1) {
916		$options['filter']['type'] = $filter['type'];
917	}
918
919	/*
920	 * Trapper and SNMP trap items contain zeros in "delay" field and, if no specific type is set, look in item types
921	 * other than trapper and SNMP trap that allow zeros. For example, when a flexible interval is used. Since trapper
922	 * and SNMP trap items contain zeros, but those zeros should not be displayed, they cannot be filtered by entering
923	 * either zero or any other number in filter field.
924	 */
925	if ($filter['delay'] !== '') {
926		if ($filter['type'] == -1 && $filter['delay'] == 0) {
927			$options['filter']['type'] = [ITEM_TYPE_ZABBIX, ITEM_TYPE_SIMPLE,  ITEM_TYPE_INTERNAL,
928				ITEM_TYPE_ZABBIX_ACTIVE, ITEM_TYPE_EXTERNAL, ITEM_TYPE_DB_MONITOR, ITEM_TYPE_IPMI,
929				ITEM_TYPE_SSH, ITEM_TYPE_TELNET, ITEM_TYPE_JMX
930			];
931			$options['filter']['delay'] = $filter['delay'];
932		}
933		elseif ($filter['type'] == ITEM_TYPE_TRAPPER || $filter['type'] == ITEM_TYPE_DEPENDENT
934				|| ($filter['type'] == ITEM_TYPE_ZABBIX_ACTIVE && strncmp($filter['key'], 'mqtt.get', 8) === 0)) {
935			$options['filter']['delay'] = -1;
936		}
937		else {
938			$options['filter']['delay'] = $filter['delay'];
939		}
940	}
941
942	if ($filter['lifetime'] !== '') {
943		$options['filter']['lifetime'] = $filter['lifetime'];
944	}
945
946	if ($filter['snmp_oid'] !== '') {
947		$options['filter']['snmp_oid'] = $filter['snmp_oid'];
948	}
949
950	if ($filter['status'] != -1) {
951		$options['filter']['status'] = $filter['status'];
952	}
953
954	if ($filter['state'] != -1) {
955		$options['filter']['status'] = ITEM_STATUS_ACTIVE;
956		$options['filter']['state'] = $filter['state'];
957	}
958
959	$data['discoveries'] = API::DiscoveryRule()->get($options);
960	$data['discoveries'] = CMacrosResolverHelper::resolveItemNames($data['discoveries']);
961
962	switch ($sort_field) {
963		case 'delay':
964			orderItemsByDelay($data['discoveries'], $sort_order, ['usermacros' => true]);
965			break;
966
967		case 'status':
968			orderItemsByStatus($data['discoveries'], $sort_order);
969			break;
970
971		default:
972			order_result($data['discoveries'], $sort_field, $sort_order);
973	}
974
975	$data['discoveries'] = expandItemNamesWithMasterItems($data['discoveries'], 'items');
976
977	// Set is_template false, when one of hosts is not template.
978	if ($data['discoveries']) {
979		$hosts_status = [];
980		foreach ($data['discoveries'] as $discovery) {
981			$hosts_status[$discovery['hosts'][0]['status']] = true;
982		}
983		foreach ($hosts_status as $key => $value) {
984			if ($key != HOST_STATUS_TEMPLATE) {
985				$data['is_template'] = false;
986				break;
987			}
988		}
989	}
990
991	// pager
992	if (hasRequest('page')) {
993		$page_num = getRequest('page');
994	}
995	elseif (isRequestMethod('get') && !hasRequest('cancel')) {
996		$page_num = 1;
997	}
998	else {
999		$page_num = CPagerHelper::loadPage($page['file']);
1000	}
1001
1002	CPagerHelper::savePage($page['file'], $page_num);
1003
1004	$data['paging'] = CPagerHelper::paginate($page_num, $data['discoveries'], $sort_order,
1005		(new CUrl('host_discovery.php'))->setArgument('context', $data['context'])
1006	);
1007
1008	$data['parent_templates'] = getItemParentTemplates($data['discoveries'], ZBX_FLAG_DISCOVERY_RULE);
1009	$data['allowed_ui_conf_templates'] = CWebUser::checkAccess(CRoleHelper::UI_CONFIGURATION_TEMPLATES);
1010
1011	// render view
1012	echo (new CView('configuration.host.discovery.list', $data))->getOutput();
1013}
1014
1015require_once dirname(__FILE__).'/include/page_footer.php';
1016