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 * Convert windows events type constant in to the string representation
24 *
25 * @param int $logtype
26 * @return string
27 */
28function get_item_logtype_description($logtype) {
29	switch ($logtype) {
30		case ITEM_LOGTYPE_INFORMATION:
31			return _('Information');
32		case ITEM_LOGTYPE_WARNING:
33			return _('Warning');
34		case ITEM_LOGTYPE_ERROR:
35			return _('Error');
36		case ITEM_LOGTYPE_FAILURE_AUDIT:
37			return _('Failure Audit');
38		case ITEM_LOGTYPE_SUCCESS_AUDIT:
39			return _('Success Audit');
40		case ITEM_LOGTYPE_CRITICAL:
41			return _('Critical');
42		case ITEM_LOGTYPE_VERBOSE:
43			return _('Verbose');
44		default:
45			return _('Unknown');
46	}
47}
48
49/**
50 * Convert windows events type constant in to the CSS style name
51 *
52 * @param int $logtype
53 * @return string
54 */
55function get_item_logtype_style($logtype) {
56	switch ($logtype) {
57		case ITEM_LOGTYPE_INFORMATION:
58		case ITEM_LOGTYPE_SUCCESS_AUDIT:
59		case ITEM_LOGTYPE_VERBOSE:
60			return ZBX_STYLE_LOG_INFO_BG;
61
62		case ITEM_LOGTYPE_WARNING:
63			return ZBX_STYLE_LOG_WARNING_BG;
64
65		case ITEM_LOGTYPE_ERROR:
66		case ITEM_LOGTYPE_FAILURE_AUDIT:
67			return ZBX_STYLE_LOG_HIGH_BG;
68
69		case ITEM_LOGTYPE_CRITICAL:
70			return ZBX_STYLE_LOG_DISASTER_BG;
71
72		default:
73			return ZBX_STYLE_LOG_NA_BG;
74	}
75}
76
77/**
78 * Get item type string name by item type number, or array of all item types if null passed.
79 *
80 * @param int|null $type
81 *
82 * @return array|string
83 */
84function item_type2str($type = null) {
85	$types = [
86		ITEM_TYPE_ZABBIX => _('Zabbix agent'),
87		ITEM_TYPE_ZABBIX_ACTIVE => _('Zabbix agent (active)'),
88		ITEM_TYPE_SIMPLE => _('Simple check'),
89		ITEM_TYPE_SNMP => _('SNMP agent'),
90		ITEM_TYPE_SNMPTRAP => _('SNMP trap'),
91		ITEM_TYPE_INTERNAL => _('Zabbix internal'),
92		ITEM_TYPE_TRAPPER => _('Zabbix trapper'),
93		ITEM_TYPE_EXTERNAL => _('External check'),
94		ITEM_TYPE_DB_MONITOR => _('Database monitor'),
95		ITEM_TYPE_HTTPAGENT => _('HTTP agent'),
96		ITEM_TYPE_IPMI => _('IPMI agent'),
97		ITEM_TYPE_SSH => _('SSH agent'),
98		ITEM_TYPE_TELNET => _('TELNET agent'),
99		ITEM_TYPE_JMX => _('JMX agent'),
100		ITEM_TYPE_CALCULATED => _('Calculated'),
101		ITEM_TYPE_HTTPTEST => _('Web monitoring'),
102		ITEM_TYPE_DEPENDENT => _('Dependent item'),
103		ITEM_TYPE_SCRIPT => _('Script')
104	];
105
106	if ($type === null) {
107		return $types;
108	}
109
110	return array_key_exists($type, $types) ? $types[$type] : _('Unknown');
111}
112
113/**
114 * Returns human readable an item value type
115 *
116 * @param int $valueType
117 *
118 * @return string
119 */
120function itemValueTypeString($valueType) {
121	switch ($valueType) {
122		case ITEM_VALUE_TYPE_UINT64:
123			return _('Numeric (unsigned)');
124		case ITEM_VALUE_TYPE_FLOAT:
125			return _('Numeric (float)');
126		case ITEM_VALUE_TYPE_STR:
127			return _('Character');
128		case ITEM_VALUE_TYPE_LOG:
129			return _('Log');
130		case ITEM_VALUE_TYPE_TEXT:
131			return _('Text');
132	}
133	return _('Unknown');
134}
135
136function item_status2str($type = null) {
137	if (is_null($type)) {
138		return [ITEM_STATUS_ACTIVE => _('Enabled'), ITEM_STATUS_DISABLED => _('Disabled')];
139	}
140
141	return ($type == ITEM_STATUS_ACTIVE) ? _('Enabled') : _('Disabled');
142}
143
144/**
145 * Returns the names of supported item states.
146 *
147 * If the $state parameter is passed, returns the name of the specific state, otherwise - returns an array of all
148 * supported states.
149 *
150 * @param string $state
151 *
152 * @return array|string
153 */
154function itemState($state = null) {
155	$states = [
156		ITEM_STATE_NORMAL => _('Normal'),
157		ITEM_STATE_NOTSUPPORTED => _('Not supported')
158	];
159
160	if ($state === null) {
161		return $states;
162	}
163	elseif (isset($states[$state])) {
164		return $states[$state];
165	}
166	else {
167		return _('Unknown');
168	}
169}
170
171/**
172 * Returns the text indicating the items status and state. If the $state parameter is not given, only the status of
173 * the item will be taken into account.
174 *
175 * @param int $status
176 * @param int $state
177 *
178 * @return string
179 */
180function itemIndicator($status, $state = null) {
181	if ($status == ITEM_STATUS_ACTIVE) {
182		return ($state == ITEM_STATE_NOTSUPPORTED) ? _('Not supported') : _('Enabled');
183	}
184
185	return _('Disabled');
186}
187
188/**
189 * Returns the CSS class for the items status and state indicator. If the $state parameter is not given, only the status of
190 * the item will be taken into account.
191 *
192 * @param int $status
193 * @param int $state
194 *
195 * @return string
196 */
197function itemIndicatorStyle($status, $state = null) {
198	if ($status == ITEM_STATUS_ACTIVE) {
199		return ($state == ITEM_STATE_NOTSUPPORTED) ?
200			ZBX_STYLE_GREY :
201			ZBX_STYLE_GREEN;
202	}
203
204	return ZBX_STYLE_RED;
205}
206
207/**
208 * Order items by keep history.
209 *
210 * @param array  $items
211 * @param string $items['history']
212 * @param string $sortorder
213 */
214function orderItemsByHistory(array &$items, $sortorder){
215	$simple_interval_parser = new CSimpleIntervalParser();
216
217	foreach ($items as &$item) {
218		$item['history_sort'] = ($simple_interval_parser->parse($item['history']) == CParser::PARSE_SUCCESS)
219			? timeUnitToSeconds($item['history'])
220			: $item['history'];
221	}
222	unset($item);
223
224	order_result($items, 'history_sort', $sortorder);
225
226	foreach ($items as &$item) {
227		unset($item['history_sort']);
228	}
229	unset($item);
230}
231
232/**
233 * Order items by keep trends.
234 *
235 * @param array  $items
236 * @param int    $items['value_type']
237 * @param string $items['trends']
238 * @param string $sortorder
239 */
240function orderItemsByTrends(array &$items, $sortorder){
241	$simple_interval_parser = new CSimpleIntervalParser();
242
243	foreach ($items as &$item) {
244		if (in_array($item['value_type'], [ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_TEXT])) {
245			$item['trends_sort'] = '';
246		}
247		else {
248			$item['trends_sort'] = ($simple_interval_parser->parse($item['trends']) == CParser::PARSE_SUCCESS)
249				? timeUnitToSeconds($item['trends'])
250				: $item['trends'];
251		}
252	}
253	unset($item);
254
255	order_result($items, 'trends_sort', $sortorder);
256
257	foreach ($items as &$item) {
258		unset($item['trends_sort']);
259	}
260	unset($item);
261}
262
263/**
264 * Order items by update interval.
265 *
266 * @param array  $items
267 * @param int    $items['type']
268 * @param string $items['delay']
269 * @param string $items['key_']
270 * @param string $sortorder
271 * @param array  $options
272 * @param bool   $options['usermacros']
273 * @param bool   $options['lldmacros']
274 */
275function orderItemsByDelay(array &$items, $sortorder, array $options){
276	$update_interval_parser = new CUpdateIntervalParser($options);
277
278	foreach ($items as &$item) {
279		if (in_array($item['type'], [ITEM_TYPE_TRAPPER, ITEM_TYPE_SNMPTRAP, ITEM_TYPE_DEPENDENT])
280				|| ($item['type'] == ITEM_TYPE_ZABBIX_ACTIVE && strncmp($item['key_'], 'mqtt.get', 8) === 0)) {
281			$item['delay_sort'] = '';
282		}
283		elseif ($update_interval_parser->parse($item['delay']) == CParser::PARSE_SUCCESS) {
284			$item['delay_sort'] = $update_interval_parser->getDelay();
285
286			if ($item['delay_sort'][0] !== '{') {
287				$item['delay_sort'] = timeUnitToSeconds($item['delay_sort']);
288			}
289		}
290		else {
291			$item['delay_sort'] = $item['delay'];
292		}
293	}
294	unset($item);
295
296	order_result($items, 'delay_sort', $sortorder);
297
298	foreach ($items as &$item) {
299		unset($item['delay_sort']);
300	}
301	unset($item);
302}
303
304/**
305 * Orders items by both status and state. Items are sorted in the following order: enabled, disabled, not supported.
306 *
307 * Keep in sync with orderTriggersByStatus().
308 *
309 * @param array  $items
310 * @param string $sortorder
311 */
312function orderItemsByStatus(array &$items, $sortorder = ZBX_SORT_UP) {
313	$sort = [];
314
315	foreach ($items as $key => $item) {
316		if ($item['status'] == ITEM_STATUS_ACTIVE) {
317			$sort[$key] = ($item['state'] == ITEM_STATE_NOTSUPPORTED) ? 2 : 0;
318		}
319		else {
320			$sort[$key] = 1;
321		}
322	}
323
324	if ($sortorder == ZBX_SORT_UP) {
325		asort($sort);
326	}
327	else {
328		arsort($sort);
329	}
330
331	$sortedItems = [];
332	foreach ($sort as $key => $val) {
333		$sortedItems[$key] = $items[$key];
334	}
335	$items = $sortedItems;
336}
337
338/**
339 * Returns the name of the given interface type. Items "status" and "state" properties must be defined.
340 *
341 * @param int $type
342 *
343 * @return null
344 */
345function interfaceType2str($type) {
346	$interfaceGroupLabels = [
347		INTERFACE_TYPE_AGENT => _('Agent'),
348		INTERFACE_TYPE_SNMP => _('SNMP'),
349		INTERFACE_TYPE_JMX => _('JMX'),
350		INTERFACE_TYPE_IPMI => _('IPMI')
351	];
352
353	return isset($interfaceGroupLabels[$type]) ? $interfaceGroupLabels[$type] : null;
354}
355
356function itemTypeInterface($type = null) {
357	$types = [
358		ITEM_TYPE_SNMP => INTERFACE_TYPE_SNMP,
359		ITEM_TYPE_SNMPTRAP => INTERFACE_TYPE_SNMP,
360		ITEM_TYPE_IPMI => INTERFACE_TYPE_IPMI,
361		ITEM_TYPE_ZABBIX => INTERFACE_TYPE_AGENT,
362		ITEM_TYPE_SIMPLE => INTERFACE_TYPE_ANY,
363		ITEM_TYPE_EXTERNAL => INTERFACE_TYPE_ANY,
364		ITEM_TYPE_SSH => INTERFACE_TYPE_ANY,
365		ITEM_TYPE_TELNET => INTERFACE_TYPE_ANY,
366		ITEM_TYPE_JMX => INTERFACE_TYPE_JMX,
367		ITEM_TYPE_HTTPAGENT => INTERFACE_TYPE_ANY
368	];
369	if (is_null($type)) {
370		return $types;
371	}
372	elseif (isset($types[$type])) {
373		return $types[$type];
374	}
375	else {
376		return false;
377	}
378}
379
380/**
381 * Copies the given items to the given hosts or templates.
382 *
383 * @param array $src_itemids  Items which will be copied to $dst_hostids.
384 * @param array $dst_hostids  Hosts and templates to whom add items.
385 *
386 * @return bool
387 */
388function copyItemsToHosts($src_itemids, $dst_hostids) {
389	$items = API::Item()->get([
390		'output' => ['type', 'snmp_oid', 'name', 'key_', 'delay', 'history', 'trends', 'status', 'value_type',
391			'trapper_hosts', 'units', 'logtimefmt', 'valuemapid', 'params', 'ipmi_sensor', 'authtype', 'username',
392			'password', 'publickey', 'privatekey', 'flags', 'description', 'inventory_link', 'jmx_endpoint',
393			'master_itemid', 'timeout', 'url', 'query_fields', 'posts', 'status_codes', 'follow_redirects',
394			'post_type', 'http_proxy', 'headers', 'retrieve_mode', 'request_method', 'output_format', 'ssl_cert_file',
395			'ssl_key_file', 'ssl_key_password', 'verify_peer', 'verify_host', 'allow_traps', 'parameters'
396		],
397		'selectPreprocessing' => ['type', 'params', 'error_handler', 'error_handler_params'],
398		'selectTags' => ['tag', 'value'],
399		'itemids' => $src_itemids,
400		'preservekeys' => true
401	]);
402
403	// Check if dependent items have master items in same selection. If not, those could be web items.
404	$master_itemids = [];
405
406	foreach ($items as $itemid => $item) {
407		if ($item['type'] == ITEM_TYPE_DEPENDENT && !array_key_exists($item['master_itemid'], $items)) {
408			$master_itemids[$item['master_itemid']] = true;
409		}
410	}
411
412	// Find same master items (that includes web items) on destination host.
413	$dst_master_items = [];
414
415	foreach (array_keys($master_itemids) as $master_itemid) {
416		$same_master_item = get_same_item_for_host(['itemid' => $master_itemid], $dst_hostids);
417
418		if ($same_master_item) {
419			$dst_master_items[$master_itemid] = $same_master_item;
420		}
421	}
422
423	$create_order = [];
424	$src_itemid_to_key = [];
425
426	// Calculate dependency level between items so that master items are created before dependent items.
427	foreach ($items as $itemid => $item) {
428		$dependency_level = 0;
429		$master_item = $item;
430		$src_itemid_to_key[$itemid] = $item['key_'];
431
432		while ($master_item['type'] == ITEM_TYPE_DEPENDENT) {
433			if (!array_key_exists($master_item['master_itemid'], $items)) {
434				break;
435			}
436
437			$master_item = $items[$master_item['master_itemid']];
438			++$dependency_level;
439		}
440
441		$create_order[$itemid] = $dependency_level;
442	}
443
444	asort($create_order);
445
446	$dstHosts = API::Host()->get([
447		'output' => ['hostid', 'host', 'status'],
448		'selectInterfaces' => ['interfaceid', 'type', 'main'],
449		'hostids' => $dst_hostids,
450		'preservekeys' => true,
451		'nopermissions' => true,
452		'templated_hosts' => true
453	]);
454
455	$src_valuemapids = array_column($items, 'valuemapid', 'valuemapid');
456	unset($src_valuemapids[0]);
457
458	if ($src_valuemapids) {
459		$valuemapids_map = [];
460		$src_valuemaps = API::ValueMap()->get([
461			'output' => ['name'],
462			'valuemapids' => $src_valuemapids,
463			'preservekeys' => true
464		]);
465		$dst_valuemaps = API::ValueMap()->get([
466			'output' => ['name', 'hostid'],
467			'hostids' => $dst_hostids,
468			'filter' => ['name' => array_column($src_valuemaps, 'name')],
469			'preservekeys' => true
470		]);
471
472		foreach ($src_valuemaps as $src_valuemapid => $src_valuemap) {
473			foreach ($dst_valuemaps as $dst_valuemapid => $dst_valuemap) {
474				if ($dst_valuemap['name'] === $src_valuemap['name']) {
475					$valuemapids_map[$src_valuemapid][$dst_valuemap['hostid']] = $dst_valuemapid;
476				}
477			}
478		}
479	}
480
481	foreach ($dstHosts as $dstHost) {
482		$interfaceids = [];
483
484		foreach ($dstHost['interfaces'] as $interface) {
485			if ($interface['main'] == 1) {
486				$interfaceids[$interface['type']] = $interface['interfaceid'];
487			}
488		}
489
490		$itemkey_to_id = [];
491		$create_items = [];
492		$current_dependency = reset($create_order);
493
494		foreach ($create_order as $itemid => $dependency_level) {
495			if ($current_dependency != $dependency_level) {
496				$current_dependency = $dependency_level;
497				$created_itemids = API::Item()->create($create_items);
498
499				if (!$created_itemids) {
500					return false;
501				}
502				$created_itemids = $created_itemids['itemids'];
503
504				foreach ($create_items as $index => $created_item) {
505					$itemkey_to_id[$created_item['key_']] = $created_itemids[$index];
506				}
507
508				$create_items = [];
509			}
510
511			$item = $items[$itemid];
512
513			if ($dstHost['status'] != HOST_STATUS_TEMPLATE) {
514				$type = itemTypeInterface($item['type']);
515
516				if ($type == INTERFACE_TYPE_ANY) {
517					foreach ([INTERFACE_TYPE_AGENT, INTERFACE_TYPE_SNMP, INTERFACE_TYPE_JMX, INTERFACE_TYPE_IPMI] as $itype) {
518						if (isset($interfaceids[$itype])) {
519							$item['interfaceid'] = $interfaceids[$itype];
520							break;
521						}
522					}
523				}
524				elseif ($type !== false) {
525					if (!isset($interfaceids[$type])) {
526						error(_s('Cannot find host interface on "%1$s" for item key "%2$s".', $dstHost['host'],
527							$item['key_']
528						));
529						return false;
530					}
531					$item['interfaceid'] = $interfaceids[$type];
532				}
533			}
534			unset($item['itemid']);
535
536			if ($item['valuemapid'] != 0) {
537				if (array_key_exists($item['valuemapid'], $valuemapids_map)
538						&& array_key_exists($dstHost['hostid'], $valuemapids_map[$item['valuemapid']])) {
539					$item['valuemapid'] = $valuemapids_map[$item['valuemapid']][$dstHost['hostid']];
540				}
541				else {
542					$item['valuemapid'] = 0;
543				}
544			}
545
546			$item['hostid'] = $dstHost['hostid'];
547
548			if ($item['type'] == ITEM_TYPE_DEPENDENT) {
549				if (array_key_exists($item['master_itemid'], $items)) {
550					$src_item_key = $src_itemid_to_key[$item['master_itemid']];
551					$item['master_itemid'] = $itemkey_to_id[$src_item_key];
552				}
553				else {
554					$item_found = false;
555
556					if (array_key_exists($item['master_itemid'], $dst_master_items)) {
557						foreach ($dst_master_items[$item['master_itemid']] as $dst_master_item) {
558							if ($dst_master_item['hostid'] == $dstHost['hostid']) {
559								// A matching item on destination host has been found.
560
561								$item['master_itemid'] = $dst_master_item['itemid'];
562								$item_found = true;
563							}
564						}
565					}
566
567					// Master item does not exist on destination host or has not been selected for copying.
568					if (!$item_found) {
569						error(_s('Item "%1$s" cannot be copied without its master item.', $item['name']));
570
571						return false;
572					}
573				}
574			}
575			else {
576				unset($item['master_itemid']);
577			}
578
579			$create_items[] = $item;
580		}
581
582		if ($create_items && !API::Item()->create($create_items)) {
583			return false;
584		}
585	}
586
587	return true;
588}
589
590function copyItems($srcHostId, $dstHostId) {
591	$srcItems = API::Item()->get([
592		'output' => ['type', 'snmp_oid', 'name', 'key_', 'delay', 'history', 'trends', 'status', 'value_type',
593			'trapper_hosts', 'units', 'logtimefmt', 'valuemapid', 'params', 'ipmi_sensor', 'authtype', 'username',
594			'password', 'publickey', 'privatekey', 'flags', 'description', 'inventory_link', 'jmx_endpoint',
595			'master_itemid', 'templateid', 'url', 'query_fields', 'timeout', 'posts', 'status_codes',
596			'follow_redirects', 'post_type', 'http_proxy', 'headers', 'retrieve_mode', 'request_method',
597			'output_format', 'ssl_cert_file', 'ssl_key_file', 'ssl_key_password', 'verify_peer', 'verify_host',
598			'allow_traps', 'parameters'
599		],
600		'selectTags' => ['tag', 'value'],
601		'selectPreprocessing' => ['type', 'params', 'error_handler', 'error_handler_params'],
602		'selectValueMap' => ['name'],
603		'hostids' => $srcHostId,
604		'webitems' => true,
605		'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL],
606		'preservekeys' => true
607	]);
608	$dstHosts = API::Host()->get([
609		'output' => ['hostid', 'host', 'status'],
610		'selectInterfaces' => ['interfaceid', 'type', 'main'],
611		'hostids' => $dstHostId,
612		'preservekeys' => true,
613		'nopermissions' => true,
614		'templated_hosts' => true
615	]);
616	$dstHost = reset($dstHosts);
617	$src_valuemap_names = [];
618	$valuemap_map = [];
619
620	foreach ($srcItems as $src_item) {
621		if ($src_item['valuemap'] && $src_item['templateid'] == 0) {
622			$src_valuemap_names[] = $src_item['valuemap']['name'];
623		}
624	}
625
626	if ($src_valuemap_names) {
627		$valuemap_map = array_column(API::ValueMap()->get([
628			'output' => ['valuemapid', 'name'],
629			'hostids' => $dstHostId,
630			'filter' => ['name' => $src_valuemap_names]
631		]), 'valuemapid', 'name');
632	}
633
634	$create_order = [];
635	$src_itemid_to_key = [];
636	foreach ($srcItems as $itemid => $item) {
637		$dependency_level = 0;
638		$master_item = $item;
639		$src_itemid_to_key[$itemid] = $item['key_'];
640
641		while ($master_item['type'] == ITEM_TYPE_DEPENDENT) {
642			$master_item = $srcItems[$master_item['master_itemid']];
643			++$dependency_level;
644		}
645
646		$create_order[$itemid] = $dependency_level;
647	}
648	asort($create_order);
649
650	$itemkey_to_id = [];
651	$create_items = [];
652	$current_dependency = reset($create_order);
653
654	foreach ($create_order as $itemid => $dependency_level) {
655		$srcItem = $srcItems[$itemid];
656
657		// Skip creating web items. Those were created before.
658		if ($srcItem['type'] == ITEM_TYPE_HTTPTEST) {
659			continue;
660		}
661
662		if ($current_dependency != $dependency_level && $create_items) {
663			$current_dependency = $dependency_level;
664			$created_itemids = API::Item()->create($create_items);
665
666			if (!$created_itemids) {
667				return false;
668			}
669			$created_itemids = $created_itemids['itemids'];
670
671			foreach ($create_items as $index => $created_item) {
672				$itemkey_to_id[$created_item['key_']] = $created_itemids[$index];
673			}
674
675			$create_items = [];
676		}
677
678		if ($srcItem['templateid'] != 0) {
679			$srcItem = get_same_item_for_host($srcItem, $dstHost['hostid']);
680
681			if (!$srcItem) {
682				return false;
683			}
684			$itemkey_to_id[$srcItem['key_']] = $srcItem['itemid'];
685			continue;
686		}
687		else if ($srcItem['valuemapid'] != 0) {
688			$srcItem['valuemapid'] = array_key_exists($srcItem['valuemap']['name'], $valuemap_map)
689				? $valuemap_map[$srcItem['valuemap']['name']]
690				: 0;
691		}
692
693		if ($dstHost['status'] != HOST_STATUS_TEMPLATE) {
694			// find a matching interface
695			$interface = CItem::findInterfaceForItem($srcItem['type'], $dstHost['interfaces']);
696			if ($interface) {
697				$srcItem['interfaceid'] = $interface['interfaceid'];
698			}
699			// no matching interface found, throw an error
700			elseif ($interface !== false) {
701				error(_s('Cannot find host interface on "%1$s" for item key "%2$s".', $dstHost['host'], $srcItem['key_']));
702			}
703		}
704		unset($srcItem['itemid']);
705		unset($srcItem['templateid']);
706		$srcItem['hostid'] = $dstHostId;
707
708		if (!$srcItem['preprocessing']) {
709			unset($srcItem['preprocessing']);
710		}
711
712		if ($srcItem['type'] == ITEM_TYPE_DEPENDENT) {
713			if ($srcItems[$srcItem['master_itemid']]['type'] == ITEM_TYPE_HTTPTEST) {
714				// Web items are outside the scope and are created before regular items.
715				$web_item = get_same_item_for_host($srcItems[$srcItem['master_itemid']], $dstHost['hostid']);
716				$srcItem['master_itemid'] = $web_item['itemid'];
717			}
718			else {
719				$src_item_key = $src_itemid_to_key[$srcItem['master_itemid']];
720				$srcItem['master_itemid'] = $itemkey_to_id[$src_item_key];
721			}
722		}
723		else {
724			unset($srcItem['master_itemid']);
725		}
726
727		$create_items[] = $srcItem;
728	}
729
730	if ($create_items && !API::Item()->create($create_items)) {
731		return false;
732	}
733
734	return true;
735}
736
737function get_item_by_itemid($itemid) {
738	$db_items = DBfetch(DBselect('SELECT i.* FROM items i WHERE i.itemid='.zbx_dbstr($itemid)));
739	if ($db_items) {
740		return $db_items;
741	}
742	error(_s('No item with itemid="%1$s".', $itemid));
743	return false;
744}
745
746/**
747 * Description:
748 * Replace items for specified host
749 *
750 * Comments:
751 * $error= true : rise Error if item doesn't exist (error generated), false: special processing (NO error generated)
752 */
753function get_same_item_for_host($item, $dest_hostids) {
754	$return_array = is_array($dest_hostids);
755	zbx_value2array($dest_hostids);
756
757	if (!is_array($item)) {
758		$itemid = $item;
759	}
760	elseif (isset($item['itemid'])) {
761		$itemid = $item['itemid'];
762	}
763
764	$same_item = null;
765	$same_items = [];
766
767	if (isset($itemid)) {
768		$db_items = DBselect(
769			'SELECT src.*'.
770			' FROM items src,items dest'.
771			' WHERE dest.itemid='.zbx_dbstr($itemid).
772				' AND src.key_=dest.key_'.
773				' AND '.dbConditionInt('src.hostid', $dest_hostids)
774		);
775		while ($db_item = DBfetch($db_items)) {
776			if (is_array($item)) {
777				$same_item = $db_item;
778				$same_items[$db_item['itemid']] = $db_item;
779			}
780			else {
781				$same_item = $db_item['itemid'];
782				$same_items[$db_item['itemid']] = $db_item['itemid'];
783			}
784		}
785		if ($return_array) {
786			return $same_items;
787		}
788		else {
789			return $same_item;
790		}
791	}
792	return false;
793}
794
795/**
796 * Get parent templates for each given item.
797 *
798 * @param array  $items                  An array of items.
799 * @param string $items[]['itemid']      ID of an item.
800 * @param string $items[]['templateid']  ID of parent template item.
801 * @param int    $flag                   Origin of the item (ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_RULE,
802 *                                       ZBX_FLAG_DISCOVERY_PROTOTYPE).
803 *
804 * @return array
805 */
806function getItemParentTemplates(array $items, $flag) {
807	$parent_itemids = [];
808	$data = [
809		'links' => [],
810		'templates' => []
811	];
812
813	foreach ($items as $item) {
814		if ($item['templateid'] != 0) {
815			$parent_itemids[$item['templateid']] = true;
816			$data['links'][$item['itemid']] = ['itemid' => $item['templateid']];
817		}
818	}
819
820	if (!$parent_itemids) {
821		return $data;
822	}
823
824	$all_parent_itemids = [];
825	$hostids = [];
826	if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
827		$lld_ruleids = [];
828	}
829
830	do {
831		if ($flag == ZBX_FLAG_DISCOVERY_RULE) {
832			$db_items = API::DiscoveryRule()->get([
833				'output' => ['itemid', 'hostid', 'templateid'],
834				'itemids' => array_keys($parent_itemids)
835			]);
836		}
837		elseif ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
838			$db_items = API::ItemPrototype()->get([
839				'output' => ['itemid', 'hostid', 'templateid'],
840				'itemids' => array_keys($parent_itemids),
841				'selectDiscoveryRule' => ['itemid']
842			]);
843		}
844		// ZBX_FLAG_DISCOVERY_NORMAL
845		else {
846			$db_items = API::Item()->get([
847				'output' => ['itemid', 'hostid', 'templateid'],
848				'itemids' => array_keys($parent_itemids),
849				'webitems' => true
850			]);
851		}
852
853		$all_parent_itemids += $parent_itemids;
854		$parent_itemids = [];
855
856		foreach ($db_items as $db_item) {
857			$data['templates'][$db_item['hostid']] = [];
858			$hostids[$db_item['itemid']] = $db_item['hostid'];
859
860			if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
861				$lld_ruleids[$db_item['itemid']] = $db_item['discoveryRule']['itemid'];
862			}
863
864			if ($db_item['templateid'] != 0) {
865				if (!array_key_exists($db_item['templateid'], $all_parent_itemids)) {
866					$parent_itemids[$db_item['templateid']] = true;
867				}
868
869				$data['links'][$db_item['itemid']] = ['itemid' => $db_item['templateid']];
870			}
871		}
872	}
873	while ($parent_itemids);
874
875	foreach ($data['links'] as &$parent_item) {
876		$parent_item['hostid'] = array_key_exists($parent_item['itemid'], $hostids)
877			? $hostids[$parent_item['itemid']]
878			: 0;
879
880		if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
881			$parent_item['lld_ruleid'] = array_key_exists($parent_item['itemid'], $lld_ruleids)
882				? $lld_ruleids[$parent_item['itemid']]
883				: 0;
884		}
885	}
886	unset($parent_item);
887
888	$db_templates = $data['templates']
889		? API::Template()->get([
890			'output' => ['name'],
891			'templateids' => array_keys($data['templates']),
892			'preservekeys' => true
893		])
894		: [];
895
896	$rw_templates = $db_templates
897		? API::Template()->get([
898			'output' => [],
899			'templateids' => array_keys($db_templates),
900			'editable' => true,
901			'preservekeys' => true
902		])
903		: [];
904
905	$data['templates'][0] = [];
906
907	foreach ($data['templates'] as $hostid => &$template) {
908		$template = array_key_exists($hostid, $db_templates)
909			? [
910				'hostid' => $hostid,
911				'name' => $db_templates[$hostid]['name'],
912				'permission' => array_key_exists($hostid, $rw_templates) ? PERM_READ_WRITE : PERM_READ
913			]
914			: [
915				'hostid' => $hostid,
916				'name' => _('Inaccessible template'),
917				'permission' => PERM_DENY
918			];
919	}
920	unset($template);
921
922	return $data;
923}
924
925/**
926 * Returns a template prefix for selected item.
927 *
928 * @param string $itemid
929 * @param array  $parent_templates  The list of the templates, prepared by getItemParentTemplates() function.
930 * @param int    $flag              Origin of the item (ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_RULE,
931 *                                  ZBX_FLAG_DISCOVERY_PROTOTYPE).
932 * @param bool   $provide_links     If this parameter is false, prefix will not contain links.
933 *
934 * @return array|null
935 */
936function makeItemTemplatePrefix($itemid, array $parent_templates, $flag, bool $provide_links) {
937	if (!array_key_exists($itemid, $parent_templates['links'])) {
938		return null;
939	}
940
941	while (array_key_exists($parent_templates['links'][$itemid]['itemid'], $parent_templates['links'])) {
942		$itemid = $parent_templates['links'][$itemid]['itemid'];
943	}
944
945	$template = $parent_templates['templates'][$parent_templates['links'][$itemid]['hostid']];
946
947	if ($provide_links && $template['permission'] == PERM_READ_WRITE) {
948		if ($flag == ZBX_FLAG_DISCOVERY_RULE) {
949			$url = (new CUrl('host_discovery.php'))
950				->setArgument('filter_set', '1')
951				->setArgument('filter_hostids', [$template['hostid']])
952				->setArgument('context', 'template');
953		}
954		elseif ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
955			$url = (new CUrl('disc_prototypes.php'))
956				->setArgument('parent_discoveryid', $parent_templates['links'][$itemid]['lld_ruleid'])
957				->setArgument('context', 'template');
958		}
959		// ZBX_FLAG_DISCOVERY_NORMAL
960		else {
961			$url = (new CUrl('items.php'))
962				->setArgument('filter_set', '1')
963				->setArgument('filter_hostids', [$template['hostid']])
964				->setArgument('context', 'template');
965		}
966
967		$name = (new CLink(CHtml::encode($template['name']), $url))->addClass(ZBX_STYLE_LINK_ALT);
968	}
969	else {
970		$name = new CSpan(CHtml::encode($template['name']));
971	}
972
973	return [$name->addClass(ZBX_STYLE_GREY), NAME_DELIMITER];
974}
975
976/**
977 * Returns a list of item templates.
978 *
979 * @param string $itemid
980 * @param array  $parent_templates  The list of the templates, prepared by getItemParentTemplates() function.
981 * @param int    $flag              Origin of the item (ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_RULE,
982 *                                  ZBX_FLAG_DISCOVERY_PROTOTYPE).
983 * @param bool   $provide_links     If this parameter is false, prefix will not contain links.
984 *
985 * @return array
986 */
987function makeItemTemplatesHtml($itemid, array $parent_templates, $flag, bool $provide_links) {
988	$list = [];
989
990	while (array_key_exists($itemid, $parent_templates['links'])) {
991		$template = $parent_templates['templates'][$parent_templates['links'][$itemid]['hostid']];
992
993		if ($provide_links && $template['permission'] == PERM_READ_WRITE) {
994			if ($flag == ZBX_FLAG_DISCOVERY_RULE) {
995				$url = (new CUrl('host_discovery.php'))
996					->setArgument('form', 'update')
997					->setArgument('itemid', $parent_templates['links'][$itemid]['itemid'])
998					->setArgument('context', 'template');
999			}
1000			elseif ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
1001				$url = (new CUrl('disc_prototypes.php'))
1002					->setArgument('form', 'update')
1003					->setArgument('itemid', $parent_templates['links'][$itemid]['itemid'])
1004					->setArgument('parent_discoveryid', $parent_templates['links'][$itemid]['lld_ruleid'])
1005					->setArgument('context', 'template');
1006			}
1007			// ZBX_FLAG_DISCOVERY_NORMAL
1008			else {
1009				$url = (new CUrl('items.php'))
1010					->setArgument('form', 'update')
1011					->setArgument('itemid', $parent_templates['links'][$itemid]['itemid'])
1012					->setArgument('context', 'template');
1013			}
1014
1015			$name = new CLink(CHtml::encode($template['name']), $url);
1016		}
1017		else {
1018			$name = (new CSpan(CHtml::encode($template['name'])))->addClass(ZBX_STYLE_GREY);
1019		}
1020
1021		array_unshift($list, $name, '&nbsp;&rArr;&nbsp;');
1022
1023		$itemid = $parent_templates['links'][$itemid]['itemid'];
1024	}
1025
1026	if ($list) {
1027		array_pop($list);
1028	}
1029
1030	return $list;
1031}
1032
1033/**
1034 * Collect latest value and actual severity value for each item of Data overview table.
1035 *
1036 * @param array $db_items
1037 * @param array $data
1038 * @param int   $show_suppressed
1039 *
1040 * @return array
1041 */
1042function getDataOverviewCellData(array $db_items, array $data, int $show_suppressed): array {
1043	$history = Manager::History()->getLastValues($db_items, 1,
1044		timeUnitToSeconds(CSettingsHelper::get(CSettingsHelper::HISTORY_PERIOD))
1045	);
1046
1047	$db_triggers = getTriggersWithActualSeverity([
1048		'output' => ['triggerid', 'priority', 'value'],
1049		'selectItems' => ['itemid'],
1050		'itemids' => array_keys($db_items),
1051		'monitored' => true,
1052		'preservekeys' => true
1053	], ['show_suppressed' => $show_suppressed]);
1054
1055	$itemid_to_triggerids = [];
1056	foreach ($db_triggers as $triggerid => $db_trigger) {
1057		foreach ($db_trigger['items'] as $item) {
1058			if (!array_key_exists($item['itemid'], $itemid_to_triggerids)) {
1059				$itemid_to_triggerids[$item['itemid']] = [];
1060			}
1061			$itemid_to_triggerids[$item['itemid']][] = $triggerid;
1062		}
1063	}
1064
1065	// Apply values and trigger severity to each $data cell.
1066	foreach ($data as &$data_clusters) {
1067		foreach ($data_clusters as &$data_cluster) {
1068			foreach ($data_cluster as &$item) {
1069				$itemid = $item['itemid'];
1070
1071				if (array_key_exists($itemid, $itemid_to_triggerids)) {
1072					$max_priority = -1;
1073					$max_priority_triggerid = -1;
1074					foreach ($itemid_to_triggerids[$itemid] as $triggerid) {
1075						$trigger = $db_triggers[$triggerid];
1076
1077						// Bump lower priority triggers of value "true" ahead of triggers with value "false".
1078						$multiplier = ($trigger['value'] == TRIGGER_VALUE_TRUE) ? TRIGGER_SEVERITY_COUNT : 0;
1079						if ($trigger['priority'] + $multiplier > $max_priority) {
1080							$max_priority_triggerid = $triggerid;
1081							$max_priority = $trigger['priority'] + $multiplier;
1082						}
1083					}
1084					$trigger = $db_triggers[$max_priority_triggerid];
1085				}
1086				else {
1087					$trigger = null;
1088				}
1089
1090				$item += [
1091					'value' => array_key_exists($itemid, $history) ? $history[$itemid][0]['value'] : null,
1092					'trigger' => $trigger
1093				];
1094			}
1095		}
1096	}
1097	unset($data_clusters, $data_cluster, $item);
1098
1099	return $data;
1100}
1101
1102/**
1103 * @param array  $groupids
1104 * @param array  $hostids
1105 * @param array  $tags
1106 * @param int    $evaltype
1107 *
1108 * @return array
1109 */
1110function getDataOverviewItems(?array $groupids = null, ?array $hostids = null, ?array $tags,
1111		int $evaltype = TAG_EVAL_TYPE_AND_OR): array {
1112
1113	if ($hostids === null) {
1114		$limit = (int) CSettingsHelper::get(CSettingsHelper::MAX_OVERVIEW_TABLE_SIZE) + 1;
1115		$db_hosts = API::Host()->get([
1116			'output' => [],
1117			'groupids' => $groupids,
1118			'monitored_hosts' => true,
1119			'with_monitored_items' => true,
1120			'preservekeys' => true,
1121			'limit' => $limit
1122		]);
1123		$hostids = array_keys($db_hosts);
1124	}
1125
1126	$db_items = API::Item()->get([
1127		'output' => ['itemid', 'hostid', 'name', 'key_', 'value_type', 'units', 'valuemapid'],
1128		'selectHosts' => ['name'],
1129		'selectValueMap' => ['mappings'],
1130		'hostids' => $hostids,
1131		'groupids' => $groupids,
1132		'evaltype' => $evaltype,
1133		'tags' => $tags,
1134		'monitored' => true,
1135		'webitems' => true,
1136		'preservekeys' => true
1137	]);
1138
1139	$db_items = CMacrosResolverHelper::resolveItemNames($db_items);
1140
1141	CArrayHelper::sort($db_items, [
1142		['field' => 'name_expanded', 'order' => ZBX_SORT_UP],
1143		['field' => 'itemid', 'order' => ZBX_SORT_UP]
1144	]);
1145
1146	return [$db_items, $hostids];
1147}
1148
1149/**
1150 * @param array  $groupids
1151 * @param array  $hostids
1152 * @param array  $filter
1153 * @param array  $filter['tags']
1154 * @param int    $filter['evaltype']
1155 * @param int    $filter['show_suppressed']
1156 *
1157 * @return array
1158 */
1159function getDataOverview(?array $groupids, ?array $hostids, array $filter): array {
1160	$tags = (array_key_exists('tags', $filter) && $filter['tags']) ? $filter['tags'] : null;
1161	$evaltype = array_key_exists('evaltype', $filter) ? $filter['evaltype'] : TAG_EVAL_TYPE_AND_OR;
1162
1163	[$db_items, $hostids] = getDataOverviewItems($groupids, $hostids, $tags, $evaltype);
1164
1165	$data = [];
1166	$item_counter = [];
1167	$db_hosts = [];
1168
1169	foreach ($db_items as $db_item) {
1170		$item_name = $db_item['name_expanded'];
1171		$host_name = $db_item['hosts'][0]['name'];
1172		$db_hosts[$db_item['hostid']] = $db_item['hosts'][0];
1173
1174		if (!array_key_exists($host_name, $item_counter)) {
1175			$item_counter[$host_name] = [];
1176		}
1177
1178		if (!array_key_exists($item_name, $item_counter[$host_name])) {
1179			$item_counter[$host_name][$item_name] = 0;
1180		}
1181
1182		$item_place = $item_counter[$host_name][$item_name];
1183		$item_counter[$host_name][$item_name]++;
1184
1185		$item = [
1186			'itemid' => $db_item['itemid'],
1187			'value_type' => $db_item['value_type'],
1188			'units' => $db_item['units'],
1189			'valuemap' => $db_item['valuemap'],
1190			'acknowledged' => array_key_exists('acknowledged', $db_item) ? $db_item['acknowledged'] : 0
1191		];
1192
1193		if (array_key_exists('triggerid', $db_item)) {
1194			$item += [
1195				'triggerid' => $db_item['triggerid'],
1196				'severity' => $db_item['priority'],
1197				'tr_value' => $db_item['value']
1198			];
1199		}
1200		else {
1201			$item += [
1202				'triggerid' => null,
1203				'severity' => null,
1204				'tr_value' => null
1205			];
1206		}
1207
1208		$data[$item_name][$item_place][$host_name] = $item;
1209	}
1210
1211	CArrayHelper::sort($db_hosts, [
1212		['field' => 'name', 'order' => ZBX_SORT_UP]
1213	]);
1214
1215	$data_display_limit = (int) CSettingsHelper::get(CSettingsHelper::MAX_OVERVIEW_TABLE_SIZE);
1216	$has_hidden_hosts = (count($db_hosts) > $data_display_limit);
1217	$db_hosts = array_slice($db_hosts, 0, $data_display_limit, true);
1218
1219	$data = array_slice($data, 0, $data_display_limit, true);
1220	$items_left = $data_display_limit;
1221	$itemids = [];
1222	array_walk($data, function (array &$item_columns) use ($data_display_limit, &$itemids, &$items_left) {
1223		if ($items_left != 0) {
1224			$item_columns = array_slice($item_columns, 0, min($data_display_limit, $items_left));
1225			$items_left -= count($item_columns);
1226		}
1227		else {
1228			$item_columns = null;
1229			return;
1230		}
1231
1232		array_walk($item_columns, function (array &$item_column) use ($data_display_limit, &$itemids) {
1233			$item_column = array_slice($item_column, 0, $data_display_limit, true);
1234			$itemids += array_column($item_column, 'itemid', 'itemid');
1235		});
1236	});
1237	$data = array_filter($data);
1238
1239	$has_hidden_items = (count($db_items) != count($itemids));
1240
1241	$db_items = array_intersect_key($db_items, $itemids);
1242	$data = getDataOverviewCellData($db_items, $data, $filter['show_suppressed']);
1243
1244	return [$data, $db_hosts, ($has_hidden_items || $has_hidden_hosts)];
1245}
1246
1247/**
1248 * Prepares interfaces select element with options.
1249 *
1250 * @param array $interfaces
1251 *
1252 * @return CSelect
1253 */
1254function getInterfaceSelect(array $interfaces): CSelect {
1255	$interface_select = new CSelect('interfaceid');
1256
1257	/** @var CSelectOption[] $options_by_type */
1258	$options_by_type = [];
1259
1260	foreach ($interfaces as $interface) {
1261		$option = new CSelectOption($interface['interfaceid'], getHostInterface($interface));
1262
1263		if ($interface['type'] == INTERFACE_TYPE_SNMP) {
1264			$option->setExtra('description', getSnmpInterfaceDescription($interface));
1265		}
1266
1267		$options_by_type[$interface['type']][] = $option;
1268	}
1269
1270	foreach ([INTERFACE_TYPE_AGENT, INTERFACE_TYPE_SNMP, INTERFACE_TYPE_JMX, INTERFACE_TYPE_IPMI] as $interface_type) {
1271		if (array_key_exists($interface_type, $options_by_type)) {
1272			$interface_group = new CSelectOptionGroup((string) interfaceType2str($interface_type));
1273
1274			if ($interface_type == INTERFACE_TYPE_SNMP) {
1275				$interface_group->setOptionTemplate('#{label}'.(new CDiv('#{description}'))->addClass('description'));
1276			}
1277
1278			$interface_group->addOptions($options_by_type[$interface_type]);
1279
1280			$interface_select->addOptionGroup($interface_group);
1281		}
1282	}
1283
1284	return $interface_select;
1285}
1286
1287/**
1288 * Creates SNMP interface description.
1289 *
1290 * @param array $interface
1291 * @param int   $interface['details']['version']        Interface SNMP version.
1292 * @param int   $interface['details']['contextname']    Interface context name for SNMP version 3.
1293 * @param int   $interface['details']['community']      Interface community for SNMP non version 3 interface.
1294 * @param int   $interface['details']['securitylevel']  Security level for SNMP version 3 interface.
1295 * @param int   $interface['details']['authprotocol']   Authentication protocol for SNMP version 3 interface.
1296 * @param int   $interface['details']['privprotocol']   Privacy protocol for SNMP version 3 interface.
1297 *
1298 * @return string
1299 */
1300function getSnmpInterfaceDescription(array $interface): string {
1301	$snmp_desc = [
1302		_s('SNMPv%1$d', $interface['details']['version'])
1303	];
1304
1305	if ($interface['details']['version'] == SNMP_V3) {
1306		$snmp_desc[] = _('Context name').': '.$interface['details']['contextname'];
1307
1308		if ($interface['details']['securitylevel'] == ITEM_SNMPV3_SECURITYLEVEL_AUTHPRIV) {
1309			[$interface['details']['authprotocol'] => $auth_protocol] = getSnmpV3AuthProtocols();
1310			[$interface['details']['privprotocol'] => $priv_protocol] = getSnmpV3PrivProtocols();
1311
1312			$snmp_desc[] = '(priv: '.$priv_protocol.', auth: '.$auth_protocol.')';
1313		}
1314		elseif ($interface['details']['securitylevel'] == ITEM_SNMPV3_SECURITYLEVEL_AUTHNOPRIV) {
1315			[$interface['details']['authprotocol'] => $auth_protocol] = getSnmpV3AuthProtocols();
1316
1317			$snmp_desc[] = '(auth: '.$auth_protocol.')';
1318		}
1319
1320	} else {
1321		$snmp_desc[] = _x('Community', 'SNMP Community').': '.$interface['details']['community'];
1322	}
1323
1324	return implode(', ', $snmp_desc);
1325}
1326
1327/**
1328 * Named SNMPv3 authentication protocols.
1329 *
1330 * @return array
1331 */
1332function getSnmpV3AuthProtocols(): array {
1333	return [
1334		ITEM_SNMPV3_AUTHPROTOCOL_MD5 => 'MD5',
1335		ITEM_SNMPV3_AUTHPROTOCOL_SHA1 => 'SHA1',
1336		ITEM_SNMPV3_AUTHPROTOCOL_SHA224 => 'SHA224',
1337		ITEM_SNMPV3_AUTHPROTOCOL_SHA256 => 'SHA256',
1338		ITEM_SNMPV3_AUTHPROTOCOL_SHA384 => 'SHA384',
1339		ITEM_SNMPV3_AUTHPROTOCOL_SHA512 => 'SHA512'
1340	];
1341}
1342
1343/**
1344 * Named SNMPv3 privacy protocols.
1345 *
1346 * @return array
1347 */
1348function getSnmpV3PrivProtocols(): array {
1349	return [
1350		ITEM_SNMPV3_PRIVPROTOCOL_DES => 'DES',
1351		ITEM_SNMPV3_PRIVPROTOCOL_AES128 => 'AES128',
1352		ITEM_SNMPV3_PRIVPROTOCOL_AES192 => 'AES192',
1353		ITEM_SNMPV3_PRIVPROTOCOL_AES256 => 'AES256',
1354		ITEM_SNMPV3_PRIVPROTOCOL_AES192C => 'AES192C',
1355		ITEM_SNMPV3_PRIVPROTOCOL_AES256C => 'AES256C'
1356	];
1357}
1358
1359/**
1360 * @param array $item
1361 * @param array $trigger
1362 *
1363 * @return CCol
1364 */
1365function getItemDataOverviewCell(array $item, ?array $trigger = null): CCol {
1366	$ack = null;
1367	$css = '';
1368	$value = UNKNOWN_VALUE;
1369
1370	if ($trigger && $trigger['value'] == TRIGGER_VALUE_TRUE) {
1371		$css = getSeverityStyle($trigger['priority']);
1372
1373		if ($trigger['problem']['acknowledged'] == 1) {
1374			$ack = [' ', (new CSpan())->addClass(ZBX_STYLE_ICON_ACKN)];
1375		}
1376	}
1377
1378	if ($item['value'] !== null) {
1379		$value = formatHistoryValue($item['value'], $item);
1380	}
1381
1382	$col = (new CCol([$value, $ack]))
1383		->addClass($css)
1384		->addClass(ZBX_STYLE_NOWRAP)
1385		->setMenuPopup(CMenuPopupHelper::getHistory($item['itemid']))
1386		->addClass(ZBX_STYLE_CURSOR_POINTER);
1387
1388	return $col;
1389}
1390
1391/**
1392 * Format history value.
1393 * First format the value according to the configuration of the item. Then apply the value mapping to the formatted (!)
1394 * value.
1395 *
1396 * @param mixed     $value
1397 * @param array     $item
1398 * @param int       $item['value_type']  type of the value: ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64, ...
1399 * @param string    $item['units']       units of item
1400 * @param array     $item['valuemap']
1401 * @param bool      $trim
1402 *
1403 * @return string
1404 */
1405function formatHistoryValue($value, array $item, $trim = true) {
1406	$mapping = false;
1407
1408	// format value
1409	if ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT || $item['value_type'] == ITEM_VALUE_TYPE_UINT64) {
1410		$value = convertUnits([
1411			'value' => $value,
1412			'units' => $item['units']
1413		]);
1414	}
1415	elseif (!in_array($item['value_type'], [ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_TEXT, ITEM_VALUE_TYPE_LOG])) {
1416		$value = _('Unknown value type');
1417	}
1418
1419	// apply value mapping
1420	switch ($item['value_type']) {
1421		case ITEM_VALUE_TYPE_STR:
1422			$mapping = CValueMapHelper::getMappedValue($item['value_type'], $value, $item['valuemap']);
1423			// break; is not missing here
1424
1425		case ITEM_VALUE_TYPE_TEXT:
1426		case ITEM_VALUE_TYPE_LOG:
1427			if ($trim && mb_strlen($value) > 20) {
1428				$value = mb_substr($value, 0, 20).'...';
1429			}
1430
1431			if ($mapping !== false) {
1432				$value = $mapping.' ('.$value.')';
1433			}
1434
1435			break;
1436
1437		default:
1438			$value = CValueMapHelper::applyValueMap($item['value_type'], $value, $item['valuemap']);
1439	}
1440
1441	return $value;
1442}
1443
1444/**
1445 * Retrieves from DB historical data for items and applies functional calculations.
1446 * If fails for some reason, returns UNRESOLVED_MACRO_STRING.
1447 *
1448 * @param array		$item
1449 * @param string	$item['value_type']	type of item, allowed: ITEM_VALUE_TYPE_FLOAT and ITEM_VALUE_TYPE_UINT64
1450 * @param string	$item['itemid']		ID of item
1451 * @param string	$item['units']		units of item
1452 * @param string	$function			function to apply to time period from param, allowed: min, max and avg
1453 * @param string	$parameter			formatted parameter for function, example: "2w" meaning 2 weeks
1454 *
1455 * @return string item functional value from history
1456 */
1457function getItemFunctionalValue($item, $function, $parameter) {
1458	// Check whether function is allowed and parameter is specified.
1459	if (!in_array($function, ['min', 'max', 'avg']) || $parameter === '') {
1460		return UNRESOLVED_MACRO_STRING;
1461	}
1462
1463	// Check whether item type is allowed for min, max and avg functions.
1464	if ($item['value_type'] != ITEM_VALUE_TYPE_FLOAT && $item['value_type'] != ITEM_VALUE_TYPE_UINT64) {
1465		return UNRESOLVED_MACRO_STRING;
1466	}
1467
1468	$number_parser = new CNumberParser(['with_suffix' => true]);
1469
1470	if ($number_parser->parse($parameter) != CParser::PARSE_SUCCESS) {
1471		return UNRESOLVED_MACRO_STRING;
1472	}
1473
1474	$parameter = $number_parser->calcValue();
1475
1476	$time_from = time() - $parameter;
1477
1478	if ($time_from < 0 || $time_from > ZBX_MAX_DATE) {
1479		return UNRESOLVED_MACRO_STRING;
1480	}
1481
1482	$result = Manager::History()->getAggregatedValue($item, $function, $time_from);
1483
1484	if ($result !== null) {
1485		return convertUnits(['value' => $result, 'units' => $item['units']]);
1486	}
1487	else {
1488		return UNRESOLVED_MACRO_STRING;
1489	}
1490}
1491
1492/**
1493 * Check if current time is within the given period.
1494 *
1495 * @param string $period	time period format: "wd[-wd2],hh:mm-hh:mm"
1496 * @param int $now			current timestamp
1497 *
1498 * @return bool		true - within period, false - out of period
1499 */
1500function checkTimePeriod($period, $now) {
1501	if (sscanf($period, '%d-%d,%d:%d-%d:%d', $d1, $d2, $h1, $m1, $h2, $m2) != 6) {
1502		if (sscanf($period, '%d,%d:%d-%d:%d', $d1, $h1, $m1, $h2, $m2) != 5) {
1503			// delay period format is wrong - skip
1504			return false;
1505		}
1506		$d2 = $d1;
1507	}
1508
1509	$tm = localtime($now, true);
1510	$day = ($tm['tm_wday'] == 0) ? 7 : $tm['tm_wday'];
1511	$sec = SEC_PER_HOUR * $tm['tm_hour'] + SEC_PER_MIN * $tm['tm_min'] + $tm['tm_sec'];
1512
1513	$sec1 = SEC_PER_HOUR * $h1 + SEC_PER_MIN * $m1;
1514	$sec2 = SEC_PER_HOUR * $h2 + SEC_PER_MIN * $m2;
1515
1516	return $d1 <= $day && $day <= $d2 && $sec1 <= $sec && $sec < $sec2;
1517}
1518
1519/**
1520 * Get item minimum delay.
1521 *
1522 * @param string $delay
1523 * @param array $flexible_intervals
1524 *
1525 * @return string
1526 */
1527function getItemDelay($delay, array $flexible_intervals) {
1528	$delay = timeUnitToSeconds($delay);
1529
1530	if ($delay != 0 || !$flexible_intervals) {
1531		return $delay;
1532	}
1533
1534	$min_delay = SEC_PER_YEAR;
1535
1536	foreach ($flexible_intervals as $flexible_interval) {
1537		$flexible_interval_parts = explode('/', $flexible_interval);
1538		$flexible_delay = timeUnitToSeconds($flexible_interval_parts[0]);
1539
1540		$min_delay = min($min_delay, $flexible_delay);
1541	}
1542
1543	return $min_delay;
1544}
1545
1546/**
1547 * Return delay value that is currently applicable
1548 *
1549 * @param int $delay					default delay
1550 * @param array $flexible_intervals		array of intervals in format: "d/wd[-wd2],hh:mm-hh:mm"
1551 * @param int $now						current timestamp
1552 *
1553 * @return int							delay for a current timestamp
1554 */
1555function getCurrentDelay($delay, array $flexible_intervals, $now) {
1556	if (!$flexible_intervals) {
1557		return $delay;
1558	}
1559
1560	$current_delay = -1;
1561
1562	foreach ($flexible_intervals as $flexible_interval) {
1563		list($flexible_delay, $flexible_period) = explode('/', $flexible_interval);
1564		$flexible_delay = (int) $flexible_delay;
1565
1566		if (($current_delay == -1 || $flexible_delay < $current_delay) && checkTimePeriod($flexible_period, $now)) {
1567			$current_delay = $flexible_delay;
1568		}
1569	}
1570
1571	if ($current_delay == -1) {
1572		return $delay;
1573	}
1574
1575	return $current_delay;
1576}
1577
1578/**
1579 * Return time of next flexible interval
1580 *
1581 * @param array $flexible_intervals  array of intervals in format: "d/wd[-wd2],hh:mm-hh:mm"
1582 * @param int $now                   current timestamp
1583 * @param int $next_interval          timestamp of a next interval
1584 *
1585 * @return bool                      false if no flexible intervals defined
1586 */
1587function getNextDelayInterval(array $flexible_intervals, $now, &$next_interval) {
1588	if (!$flexible_intervals) {
1589		return false;
1590	}
1591
1592	$next = 0;
1593	$tm = localtime($now, true);
1594	$day = ($tm['tm_wday'] == 0) ? 7 : $tm['tm_wday'];
1595	$sec = SEC_PER_HOUR * $tm['tm_hour'] + SEC_PER_MIN * $tm['tm_min'] + $tm['tm_sec'];
1596
1597	foreach ($flexible_intervals as $flexible_interval) {
1598		$flexible_interval_parts = explode('/', $flexible_interval);
1599
1600		if (sscanf($flexible_interval_parts[1], '%d-%d,%d:%d-%d:%d', $d1, $d2, $h1, $m1, $h2, $m2) != 6) {
1601			if (sscanf($flexible_interval_parts[1], '%d,%d:%d-%d:%d', $d1, $h1, $m1, $h2, $m2) != 5) {
1602				continue;
1603			}
1604			$d2 = $d1;
1605		}
1606
1607		$sec1 = SEC_PER_HOUR * $h1 + SEC_PER_MIN * $m1;
1608		$sec2 = SEC_PER_HOUR * $h2 + SEC_PER_MIN * $m2;
1609
1610		// current period
1611		if ($d1 <= $day && $day <= $d2 && $sec1 <= $sec && $sec < $sec2) {
1612			if ($next == 0 || $next > $now - $sec + $sec2) {
1613				// the next second after the current interval's upper bound
1614				$next = $now - $sec + $sec2;
1615			}
1616		}
1617		// will be active today
1618		elseif ($d1 <= $day && $d2 >= $day && $sec < $sec1) {
1619			if ($next == 0 || $next > $now - $sec + $sec1) {
1620				$next = $now - $sec + $sec1;
1621			}
1622		}
1623		else {
1624			$nextDay = ($day + 1 <= 7) ? $day + 1 : 1;
1625
1626			// will be active tomorrow
1627			if ($d1 <= $nextDay && $nextDay <= $d2) {
1628				if ($next == 0 || $next > $now - $sec + SEC_PER_DAY + $sec1) {
1629					$next = $now - $sec + SEC_PER_DAY + $sec1;
1630				}
1631			}
1632			// later in the future
1633			else {
1634				$dayDiff = -1;
1635
1636				if ($day < $d1) {
1637					$dayDiff = $d1 - $day;
1638				}
1639				if ($day >= $d2) {
1640					$dayDiff = ($d1 + 7) - $day;
1641				}
1642				if ($d1 <= $day && $day < $d2) {
1643					// should never happen, could not deduce day difference
1644					$dayDiff = -1;
1645				}
1646				if ($dayDiff != -1 && ($next == 0 || $next > $now - $sec + SEC_PER_DAY * $dayDiff + $sec1)) {
1647					$next = $now - $sec + SEC_PER_DAY * $dayDiff + $sec1;
1648				}
1649			}
1650		}
1651	}
1652
1653	if ($next != 0) {
1654		$next_interval = $next;
1655	}
1656
1657	return ($next != 0);
1658}
1659
1660/**
1661 * Calculate nextcheck timestamp for an item using flexible intervals.
1662 *
1663 * the parameter $flexible_intervals is an array if strings that are in the following format:
1664 *
1665 *           +------------[;]<----------+
1666 *           |                          |
1667 *         ->+-[d/wd[-wd2],hh:mm-hh:mm]-+
1668 *
1669 *         d       - delay (0-n)
1670 *         wd, wd2 - day of week (1-7)
1671 *         hh      - hours (0-24)
1672 *         mm      - minutes (0-59)
1673 *
1674 * @param int $seed						seed value applied to delay to spread item checks over the delay period
1675 * @param string $delay					default delay, can be overridden
1676 * @param array $flexible_intervals		array of flexible intervals
1677 * @param int $now						current timestamp
1678 *
1679 * @return int
1680 */
1681function calculateItemNextCheck($seed, $delay, $flexible_intervals, $now) {
1682	/*
1683	 * Try to find the nearest 'nextcheck' value with condition 'now' < 'nextcheck' < 'now' + SEC_PER_YEAR
1684	 * If it is not possible to check the item within a year, fail.
1685	 */
1686
1687	$t = $now;
1688	$tMax = $now + SEC_PER_YEAR;
1689	$try = 0;
1690
1691	while ($t < $tMax) {
1692		// Calculate 'nextcheck' value for the current interval.
1693		$currentDelay = getCurrentDelay($delay, $flexible_intervals, $t);
1694
1695		if ($currentDelay != 0) {
1696			$nextCheck = $currentDelay * floor($t / $currentDelay) + ($seed % $currentDelay);
1697
1698			if ($try == 0) {
1699				while ($nextCheck <= $t) {
1700					$nextCheck += $currentDelay;
1701				}
1702			}
1703			else {
1704				while ($nextCheck < $t) {
1705					$nextCheck += $currentDelay;
1706				}
1707			}
1708		}
1709		else {
1710			$nextCheck = ZBX_JAN_2038;
1711		}
1712
1713		/*
1714		 * Is 'nextcheck' < end of the current interval and the end of the current interval
1715		 * is the beginning of the next interval - 1.
1716		 */
1717		if (getNextDelayInterval($flexible_intervals, $t, $nextInterval) && $nextCheck >= $nextInterval) {
1718			// 'nextcheck' is beyond the current interval.
1719			$t = $nextInterval;
1720			$try++;
1721		}
1722		else {
1723			break;
1724		}
1725	}
1726
1727	return $nextCheck;
1728}
1729
1730/*
1731 * Description:
1732 *	Function returns true if http items exists in the $items array.
1733 *	The array should contain a field 'type'
1734 */
1735function httpItemExists($items) {
1736	foreach ($items as $item) {
1737		if ($item['type'] == ITEM_TYPE_HTTPTEST) {
1738			return true;
1739		}
1740	}
1741	return false;
1742}
1743
1744function getParamFieldNameByType($itemType) {
1745	switch ($itemType) {
1746		case ITEM_TYPE_SCRIPT:
1747			return 'script';
1748		case ITEM_TYPE_SSH:
1749		case ITEM_TYPE_TELNET:
1750		case ITEM_TYPE_JMX:
1751			return 'params_es';
1752		case ITEM_TYPE_DB_MONITOR:
1753			return 'params_ap';
1754		case ITEM_TYPE_CALCULATED:
1755			return 'params_f';
1756		default:
1757			return 'params';
1758	}
1759}
1760
1761function getParamFieldLabelByType($itemType) {
1762	switch ($itemType) {
1763		case ITEM_TYPE_SCRIPT:
1764			return _('Script');
1765		case ITEM_TYPE_SSH:
1766		case ITEM_TYPE_TELNET:
1767		case ITEM_TYPE_JMX:
1768			return _('Executed script');
1769		case ITEM_TYPE_DB_MONITOR:
1770			return _('SQL query');
1771		case ITEM_TYPE_CALCULATED:
1772			return _('Formula');
1773		default:
1774			return 'params';
1775	}
1776}
1777
1778/**
1779 * Get either one or all item preprocessing types.
1780 * If $grouped set to true, returns group labels. Returns empty string if no specific type is found.
1781 *
1782 * Usage examples:
1783 *    - get_preprocessing_types(null, true, [5, 4, 2])             Returns array as defined.
1784 *    - get_preprocessing_types(4, true, [5, 4, 2])                Returns string: 'Trim'.
1785 *    - get_preprocessing_types(<wrong type>, true, [5, 4, 2])     Returns an empty string: ''.
1786 *    - get_preprocessing_types(null, false, [5, 12, 15, 16, 20])  Returns subarrays in one array maintaining index:
1787 *                                                                     [5] => Regular expression
1788 *                                                                     [12] => JSONPath
1789 *                                                                     [15] => Does not match regular expression
1790 *                                                                     [16] => Check for error in JSON
1791 *                                                                     [20] => Discard unchanged with heartbeat
1792 *
1793 * @param int   $type             Item preprocessing type.
1794 * @param bool  $grouped          Group label flag. If specific type is given, this parameter does not matter.
1795 * @param array $supported_types  Array of supported pre-processing types. If none are given, empty array is returned.
1796 *
1797 * @return array|string
1798 */
1799function get_preprocessing_types($type = null, $grouped = true, array $supported_types = []) {
1800	$types = [
1801		ZBX_PREPROC_REGSUB => [
1802			'group' => _('Text'),
1803			'name' => _('Regular expression')
1804		],
1805		ZBX_PREPROC_STR_REPLACE => [
1806			'group' => _('Text'),
1807			'name' => _('Replace')
1808		],
1809		ZBX_PREPROC_TRIM => [
1810			'group' => _('Text'),
1811			'name' => _('Trim')
1812		],
1813		ZBX_PREPROC_RTRIM => [
1814			'group' => _('Text'),
1815			'name' => _('Right trim')
1816		],
1817		ZBX_PREPROC_LTRIM => [
1818			'group' => _('Text'),
1819			'name' => _('Left trim')
1820		],
1821		ZBX_PREPROC_XPATH => [
1822			'group' => _('Structured data'),
1823			'name' => _('XML XPath')
1824		],
1825		ZBX_PREPROC_JSONPATH => [
1826			'group' => _('Structured data'),
1827			'name' => _('JSONPath')
1828		],
1829		ZBX_PREPROC_CSV_TO_JSON => [
1830			'group' => _('Structured data'),
1831			'name' => _('CSV to JSON')
1832		],
1833		ZBX_PREPROC_XML_TO_JSON => [
1834			'group' => _('Structured data'),
1835			'name' => _('XML to JSON')
1836		],
1837		ZBX_PREPROC_MULTIPLIER => [
1838			'group' => _('Arithmetic'),
1839			'name' => _('Custom multiplier')
1840		],
1841		ZBX_PREPROC_DELTA_VALUE => [
1842			'group' => _x('Change', 'noun'),
1843			'name' => _('Simple change')
1844		],
1845		ZBX_PREPROC_DELTA_SPEED => [
1846			'group' => _x('Change', 'noun'),
1847			'name' => _('Change per second')
1848		],
1849		ZBX_PREPROC_BOOL2DEC => [
1850			'group' => _('Numeral systems'),
1851			'name' => _('Boolean to decimal')
1852		],
1853		ZBX_PREPROC_OCT2DEC => [
1854			'group' => _('Numeral systems'),
1855			'name' => _('Octal to decimal')
1856		],
1857		ZBX_PREPROC_HEX2DEC => [
1858			'group' => _('Numeral systems'),
1859			'name' => _('Hexadecimal to decimal')
1860		],
1861		ZBX_PREPROC_SCRIPT => [
1862			'group' => _('Custom scripts'),
1863			'name' => _('JavaScript')
1864		],
1865		ZBX_PREPROC_VALIDATE_RANGE => [
1866			'group' => _('Validation'),
1867			'name' => _('In range')
1868		],
1869		ZBX_PREPROC_VALIDATE_REGEX => [
1870			'group' => _('Validation'),
1871			'name' => _('Matches regular expression')
1872		],
1873		ZBX_PREPROC_VALIDATE_NOT_REGEX => [
1874			'group' => _('Validation'),
1875			'name' => _('Does not match regular expression')
1876		],
1877		ZBX_PREPROC_ERROR_FIELD_JSON => [
1878			'group' => _('Validation'),
1879			'name' => _('Check for error in JSON')
1880		],
1881		ZBX_PREPROC_ERROR_FIELD_XML => [
1882			'group' => _('Validation'),
1883			'name' => _('Check for error in XML')
1884		],
1885		ZBX_PREPROC_ERROR_FIELD_REGEX => [
1886			'group' => _('Validation'),
1887			'name' => _('Check for error using regular expression')
1888		],
1889		ZBX_PREPROC_VALIDATE_NOT_SUPPORTED => [
1890			'group' => _('Validation'),
1891			'name' => _('Check for not supported value')
1892		],
1893		ZBX_PREPROC_THROTTLE_VALUE => [
1894			'group' => _('Throttling'),
1895			'name' => _('Discard unchanged')
1896		],
1897		ZBX_PREPROC_THROTTLE_TIMED_VALUE => [
1898			'group' => _('Throttling'),
1899			'name' => _('Discard unchanged with heartbeat')
1900		],
1901		ZBX_PREPROC_PROMETHEUS_PATTERN => [
1902			'group' => _('Prometheus'),
1903			'name' => _('Prometheus pattern')
1904		],
1905		ZBX_PREPROC_PROMETHEUS_TO_JSON => [
1906			'group' => _('Prometheus'),
1907			'name' => _('Prometheus to JSON')
1908		]
1909	];
1910
1911	$filtered_types = [];
1912
1913	foreach ($types as $_type => $data) {
1914		if (in_array($_type, $supported_types)) {
1915			$filtered_types[$data['group']][$_type] = $data['name'];
1916		}
1917	}
1918
1919	$groups = [];
1920
1921	foreach ($filtered_types as $label => $types) {
1922		$groups[] = [
1923			'label' => $label,
1924			'types' => $types
1925		];
1926	}
1927
1928	if ($type !== null) {
1929		foreach ($groups as $group) {
1930			if (array_key_exists($type, $group['types'])) {
1931				return $group['types'][$type];
1932			}
1933		}
1934
1935		return '';
1936	}
1937	elseif ($grouped) {
1938		return $groups;
1939	}
1940	else {
1941		$types = [];
1942
1943		foreach ($groups as $group) {
1944			$types += $group['types'];
1945		}
1946
1947		return $types;
1948	}
1949}
1950
1951/*
1952 * Quoting $param if it contain special characters.
1953 *
1954 * @param string $param
1955 * @param bool   $forced
1956 *
1957 * @return string
1958 */
1959function quoteItemKeyParam($param, $forced = false) {
1960	if (!$forced) {
1961		if (!isset($param[0]) || ($param[0] != '"' && false === strpbrk($param, ',]'))) {
1962			return $param;
1963		}
1964	}
1965
1966	return '"'.str_replace('"', '\\"', $param).'"';
1967}
1968
1969/**
1970 * Expands item name and for dependent item master item name.
1971 *
1972 * @param array  $items        Array of items.
1973 * @param string $data_source  'items' or 'itemprototypes'.
1974 *
1975 * @return array
1976 */
1977function expandItemNamesWithMasterItems($items, $data_source) {
1978	$items = CMacrosResolverHelper::resolveItemNames($items);
1979	$itemids = [];
1980	$master_itemids = [];
1981
1982	foreach ($items as $item_index => &$item) {
1983		if ($item['type'] == ITEM_TYPE_DEPENDENT) {
1984			$master_itemids[$item['master_itemid']] = true;
1985		}
1986
1987		// The "source" is required to tell the frontend where the link should point at - item or item prototype.
1988		$item['source'] = $data_source;
1989		$itemids[$item_index] = $item['itemid'];
1990	}
1991	unset($item);
1992
1993	$master_itemids = array_diff(array_keys($master_itemids), $itemids);
1994
1995	if ($master_itemids) {
1996		$options = [
1997			'output' => ['itemid', 'type', 'hostid', 'name', 'key_'],
1998			'itemids' => $master_itemids,
1999			'editable' => true,
2000			'preservekeys' => true
2001		];
2002		$master_items = API::Item()->get($options + ['webitems' => true]);
2003
2004		foreach ($master_items as &$master_item) {
2005			$master_item['source'] = 'items';
2006		}
2007		unset($master_item);
2008
2009		$master_item_prototypes = API::ItemPrototype()->get($options);
2010
2011		foreach ($master_item_prototypes as &$master_item_prototype) {
2012			$master_item_prototype['source'] = 'itemprototypes';
2013		}
2014		unset($master_item_prototype);
2015
2016		$master_items = CMacrosResolverHelper::resolveItemNames($master_items + $master_item_prototypes);
2017	}
2018
2019	foreach ($items as &$item) {
2020		if ($item['type'] == ITEM_TYPE_DEPENDENT) {
2021			$master_itemid = $item['master_itemid'];
2022			$items_index = array_search($master_itemid, $itemids);
2023
2024			$item['master_item'] = [
2025				'itemid' => $master_itemid,
2026				'name_expanded' => ($items_index === false)
2027					? $master_items[$master_itemid]['name_expanded']
2028					: $items[$items_index]['name_expanded'],
2029				'type' => ($items_index === false)
2030					? $master_items[$master_itemid]['type']
2031					: $items[$items_index]['type'],
2032				'source' => ($items_index === false)
2033					? $master_items[$master_itemid]['source']
2034					: $items[$items_index]['source']
2035			];
2036		}
2037	}
2038	unset($item);
2039
2040	return $items;
2041}
2042
2043/**
2044 * Returns an array of allowed item types for "Check now" functionality.
2045 *
2046 * @return array
2047 */
2048function checkNowAllowedTypes() {
2049	return [
2050		ITEM_TYPE_ZABBIX,
2051		ITEM_TYPE_SIMPLE,
2052		ITEM_TYPE_INTERNAL,
2053		ITEM_TYPE_EXTERNAL,
2054		ITEM_TYPE_DB_MONITOR,
2055		ITEM_TYPE_IPMI,
2056		ITEM_TYPE_SSH,
2057		ITEM_TYPE_TELNET,
2058		ITEM_TYPE_CALCULATED,
2059		ITEM_TYPE_JMX,
2060		ITEM_TYPE_HTTPAGENT,
2061		ITEM_TYPE_SCRIPT,
2062		ITEM_TYPE_SNMP
2063	];
2064}
2065
2066/**
2067 * Validates update interval for items, item prototypes and low-level discovery rules and their overrides.
2068 *
2069 * @param CUpdateIntervalParser $parser [IN]      Parser used for delay validation.
2070 * @param string                $value  [IN]      Update interval to parse and validate.
2071 * @param string                $field_name [IN]  Frontend or API field name in the error
2072 * @param string                $error  [OUT]     Returned error string if delay validation fails.
2073 *
2074 * @return bool
2075 */
2076function validateDelay(CUpdateIntervalParser $parser, $field_name, $value, &$error) {
2077	if ($parser->parse($value) != CParser::PARSE_SUCCESS) {
2078		$error = _s('Incorrect value for field "%1$s": %2$s.', $field_name, _('invalid delay'));
2079
2080		return false;
2081	}
2082
2083	$delay = $parser->getDelay();
2084
2085	if ($delay[0] !== '{') {
2086		$delay_sec = timeUnitToSeconds($delay);
2087		$intervals = $parser->getIntervals();
2088		$flexible_intervals = $parser->getIntervals(ITEM_DELAY_FLEXIBLE);
2089		$has_scheduling_intervals = (bool) $parser->getIntervals(ITEM_DELAY_SCHEDULING);
2090		$has_macros = false;
2091
2092		foreach ($intervals as $interval) {
2093			if (strpos($interval['interval'], '{') !== false) {
2094				$has_macros = true;
2095				break;
2096			}
2097		}
2098
2099		// If delay is 0, there must be at least one either flexible or scheduling interval.
2100		if ($delay_sec == 0 && !$intervals) {
2101			$error = _('Item will not be refreshed. Specified update interval requires having at least one either flexible or scheduling interval.');
2102
2103			return false;
2104		}
2105		elseif ($delay_sec < 0 || $delay_sec > SEC_PER_DAY) {
2106			$error = _('Item will not be refreshed. Update interval should be between 1s and 1d. Also Scheduled/Flexible intervals can be used.');
2107
2108			return false;
2109		}
2110
2111		// If there are scheduling intervals or intervals with macros, skip the next check calculation.
2112		if (!$has_macros && !$has_scheduling_intervals && $flexible_intervals
2113				&& calculateItemNextCheck(0, $delay_sec, $flexible_intervals, time()) == ZBX_JAN_2038) {
2114			$error = _('Item will not be refreshed. Please enter a correct update interval.');
2115
2116			return false;
2117		}
2118	}
2119
2120	return true;
2121}
2122