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 * @return array|string
82 */
83function item_type2str($type = null) {
84	$types = [
85		ITEM_TYPE_ZABBIX => _('Zabbix agent'),
86		ITEM_TYPE_ZABBIX_ACTIVE => _('Zabbix agent (active)'),
87		ITEM_TYPE_SIMPLE => _('Simple check'),
88		ITEM_TYPE_SNMPV1 => _('SNMPv1 agent'),
89		ITEM_TYPE_SNMPV2C => _('SNMPv2 agent'),
90		ITEM_TYPE_SNMPV3 => _('SNMPv3 agent'),
91		ITEM_TYPE_SNMPTRAP => _('SNMP trap'),
92		ITEM_TYPE_INTERNAL => _('Zabbix internal'),
93		ITEM_TYPE_TRAPPER => _('Zabbix trapper'),
94		ITEM_TYPE_AGGREGATE => _('Zabbix aggregate'),
95		ITEM_TYPE_EXTERNAL => _('External check'),
96		ITEM_TYPE_DB_MONITOR => _('Database monitor'),
97		ITEM_TYPE_HTTPAGENT => _('HTTP agent'),
98		ITEM_TYPE_IPMI => _('IPMI agent'),
99		ITEM_TYPE_SSH => _('SSH agent'),
100		ITEM_TYPE_TELNET => _('TELNET agent'),
101		ITEM_TYPE_JMX => _('JMX agent'),
102		ITEM_TYPE_CALCULATED => _('Calculated'),
103		ITEM_TYPE_HTTPTEST => _('Web monitoring'),
104		ITEM_TYPE_DEPENDENT => _('Dependent item')
105	];
106	if (is_null($type)) {
107		return $types;
108	}
109	elseif (isset($types[$type])) {
110		return $types[$type];
111	}
112	else {
113		return _('Unknown');
114	}
115}
116
117/**
118 * Returns human readable an item value type
119 *
120 * @param int $valueType
121 *
122 * @return string
123 */
124function itemValueTypeString($valueType) {
125	switch ($valueType) {
126		case ITEM_VALUE_TYPE_UINT64:
127			return _('Numeric (unsigned)');
128		case ITEM_VALUE_TYPE_FLOAT:
129			return _('Numeric (float)');
130		case ITEM_VALUE_TYPE_STR:
131			return _('Character');
132		case ITEM_VALUE_TYPE_LOG:
133			return _('Log');
134		case ITEM_VALUE_TYPE_TEXT:
135			return _('Text');
136	}
137	return _('Unknown');
138}
139
140function item_status2str($type = null) {
141	if (is_null($type)) {
142		return [ITEM_STATUS_ACTIVE => _('Enabled'), ITEM_STATUS_DISABLED => _('Disabled')];
143	}
144
145	return ($type == ITEM_STATUS_ACTIVE) ? _('Enabled') : _('Disabled');
146}
147
148/**
149 * Returns the names of supported item states.
150 *
151 * If the $state parameter is passed, returns the name of the specific state, otherwise - returns an array of all
152 * supported states.
153 *
154 * @param string $state
155 *
156 * @return array|string
157 */
158function itemState($state = null) {
159	$states = [
160		ITEM_STATE_NORMAL => _('Normal'),
161		ITEM_STATE_NOTSUPPORTED => _('Not supported')
162	];
163
164	if ($state === null) {
165		return $states;
166	}
167	elseif (isset($states[$state])) {
168		return $states[$state];
169	}
170	else {
171		return _('Unknown');
172	}
173}
174
175/**
176 * Returns the text indicating the items status and state. If the $state parameter is not given, only the status of
177 * the item will be taken into account.
178 *
179 * @param int $status
180 * @param int $state
181 *
182 * @return string
183 */
184function itemIndicator($status, $state = null) {
185	if ($status == ITEM_STATUS_ACTIVE) {
186		return ($state == ITEM_STATE_NOTSUPPORTED) ? _('Not supported') : _('Enabled');
187	}
188
189	return _('Disabled');
190}
191
192/**
193 * Returns the CSS class for the items status and state indicator. If the $state parameter is not given, only the status of
194 * the item will be taken into account.
195 *
196 * @param int $status
197 * @param int $state
198 *
199 * @return string
200 */
201function itemIndicatorStyle($status, $state = null) {
202	if ($status == ITEM_STATUS_ACTIVE) {
203		return ($state == ITEM_STATE_NOTSUPPORTED) ?
204			ZBX_STYLE_GREY :
205			ZBX_STYLE_GREEN;
206	}
207
208	return ZBX_STYLE_RED;
209}
210
211/**
212 * Order items by keep history.
213 *
214 * @param array  $items
215 * @param string $items['history']
216 * @param string $sortorder
217 */
218function orderItemsByHistory(array &$items, $sortorder){
219	$simple_interval_parser = new CSimpleIntervalParser();
220
221	foreach ($items as &$item) {
222		$item['history_sort'] = ($simple_interval_parser->parse($item['history']) == CParser::PARSE_SUCCESS)
223			? timeUnitToSeconds($item['history'])
224			: $item['history'];
225	}
226	unset($item);
227
228	order_result($items, 'history_sort', $sortorder);
229
230	foreach ($items as &$item) {
231		unset($item['history_sort']);
232	}
233	unset($item);
234}
235
236/**
237 * Order items by keep trends.
238 *
239 * @param array  $items
240 * @param int    $items['value_type']
241 * @param string $items['trends']
242 * @param string $sortorder
243 */
244function orderItemsByTrends(array &$items, $sortorder){
245	$simple_interval_parser = new CSimpleIntervalParser();
246
247	foreach ($items as &$item) {
248		if (in_array($item['value_type'], [ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_TEXT])) {
249			$item['trends_sort'] = '';
250		}
251		else {
252			$item['trends_sort'] = ($simple_interval_parser->parse($item['trends']) == CParser::PARSE_SUCCESS)
253				? timeUnitToSeconds($item['trends'])
254				: $item['trends'];
255		}
256	}
257	unset($item);
258
259	order_result($items, 'trends_sort', $sortorder);
260
261	foreach ($items as &$item) {
262		unset($item['trends_sort']);
263	}
264	unset($item);
265}
266
267/**
268 * Order items by update interval.
269 *
270 * @param array  $items
271 * @param int    $items['type']
272 * @param string $items['delay']
273 * @param string $sortorder
274 * @param array  $options
275 * @param bool   $options['usermacros']
276 * @param bool   $options['lldmacros']
277 */
278function orderItemsByDelay(array &$items, $sortorder, array $options){
279	$update_interval_parser = new CUpdateIntervalParser($options);
280
281	foreach ($items as &$item) {
282		if (in_array($item['type'], [ITEM_TYPE_TRAPPER, ITEM_TYPE_SNMPTRAP, ITEM_TYPE_DEPENDENT])) {
283			$item['delay_sort'] = '';
284		}
285		elseif ($update_interval_parser->parse($item['delay']) == CParser::PARSE_SUCCESS) {
286			$item['delay_sort'] = $update_interval_parser->getDelay();
287
288			if ($item['delay_sort'][0] !== '{') {
289				$item['delay_sort'] = timeUnitToSeconds($item['delay_sort']);
290			}
291		}
292		else {
293			$item['delay_sort'] = $item['delay'];
294		}
295	}
296	unset($item);
297
298	order_result($items, 'delay_sort', $sortorder);
299
300	foreach ($items as &$item) {
301		unset($item['delay_sort']);
302	}
303	unset($item);
304}
305
306/**
307 * Orders items by both status and state. Items are sorted in the following order: enabled, disabled, not supported.
308 *
309 * Keep in sync with orderTriggersByStatus().
310 *
311 * @param array  $items
312 * @param string $sortorder
313 */
314function orderItemsByStatus(array &$items, $sortorder = ZBX_SORT_UP) {
315	$sort = [];
316
317	foreach ($items as $key => $item) {
318		if ($item['status'] == ITEM_STATUS_ACTIVE) {
319			$sort[$key] = ($item['state'] == ITEM_STATE_NOTSUPPORTED) ? 2 : 0;
320		}
321		else {
322			$sort[$key] = 1;
323		}
324	}
325
326	if ($sortorder == ZBX_SORT_UP) {
327		asort($sort);
328	}
329	else {
330		arsort($sort);
331	}
332
333	$sortedItems = [];
334	foreach ($sort as $key => $val) {
335		$sortedItems[$key] = $items[$key];
336	}
337	$items = $sortedItems;
338}
339
340/**
341 * Returns the name of the given interface type. Items "status" and "state" properties must be defined.
342 *
343 * @param int $type
344 *
345 * @return null
346 */
347function interfaceType2str($type) {
348	$interfaceGroupLabels = [
349		INTERFACE_TYPE_AGENT => _('Agent'),
350		INTERFACE_TYPE_SNMP => _('SNMP'),
351		INTERFACE_TYPE_JMX => _('JMX'),
352		INTERFACE_TYPE_IPMI => _('IPMI')
353	];
354
355	return isset($interfaceGroupLabels[$type]) ? $interfaceGroupLabels[$type] : null;
356}
357
358function itemTypeInterface($type = null) {
359	$types = [
360		ITEM_TYPE_SNMPV1 => INTERFACE_TYPE_SNMP,
361		ITEM_TYPE_SNMPV2C => INTERFACE_TYPE_SNMP,
362		ITEM_TYPE_SNMPV3 => INTERFACE_TYPE_SNMP,
363		ITEM_TYPE_SNMPTRAP => INTERFACE_TYPE_SNMP,
364		ITEM_TYPE_IPMI => INTERFACE_TYPE_IPMI,
365		ITEM_TYPE_ZABBIX => INTERFACE_TYPE_AGENT,
366		ITEM_TYPE_SIMPLE => INTERFACE_TYPE_ANY,
367		ITEM_TYPE_EXTERNAL => INTERFACE_TYPE_ANY,
368		ITEM_TYPE_SSH => INTERFACE_TYPE_ANY,
369		ITEM_TYPE_TELNET => INTERFACE_TYPE_ANY,
370		ITEM_TYPE_JMX => INTERFACE_TYPE_JMX,
371		ITEM_TYPE_HTTPAGENT => INTERFACE_TYPE_ANY
372	];
373	if (is_null($type)) {
374		return $types;
375	}
376	elseif (isset($types[$type])) {
377		return $types[$type];
378	}
379	else {
380		return false;
381	}
382}
383
384/**
385 * Copies the given items to the given hosts or templates.
386 *
387 * @param array $src_itemids  Items which will be copied to $dst_hostids.
388 * @param array $dst_hostids  Hosts and templates to whom add items.
389 *
390 * @return bool
391 */
392function copyItemsToHosts($src_itemids, $dst_hostids) {
393	$items = API::Item()->get([
394		'output' => ['type', 'snmp_community', 'snmp_oid', 'name', 'key_', 'delay', 'history', 'trends', 'status',
395			'value_type', 'trapper_hosts', 'units', 'snmpv3_contextname', 'snmpv3_securityname', 'snmpv3_securitylevel',
396			'snmpv3_authprotocol', 'snmpv3_authpassphrase', 'snmpv3_privprotocol', 'snmpv3_privpassphrase',
397			'logtimefmt', 'valuemapid', 'params', 'ipmi_sensor', 'authtype', 'username', 'password', 'publickey',
398			'privatekey', 'flags', 'port', 'description', 'inventory_link', 'jmx_endpoint', 'master_itemid', 'timeout',
399			'url', 'query_fields', 'posts', 'status_codes', 'follow_redirects', 'post_type', 'http_proxy', 'headers',
400			'retrieve_mode', 'request_method', 'output_format', 'ssl_cert_file', 'ssl_key_file', 'ssl_key_password',
401			'verify_peer', 'verify_host', 'allow_traps'
402		],
403		'selectApplications' => ['applicationid'],
404		'selectPreprocessing' => ['type', 'params'],
405		'itemids' => $src_itemids,
406		'preservekeys' => true
407	]);
408
409	// Check if dependent items have master items in same selection. If not, those could be web items.
410	$master_itemids = [];
411
412	foreach ($items as $itemid => $item) {
413		if ($item['type'] == ITEM_TYPE_DEPENDENT && !array_key_exists($item['master_itemid'], $items)) {
414			$master_itemids[$item['master_itemid']] = true;
415		}
416	}
417
418	// Find same master items (that includes web items) on destination host.
419	$dst_master_items = [];
420
421	foreach (array_keys($master_itemids) as $master_itemid) {
422		$same_master_item = get_same_item_for_host(['itemid' => $master_itemid], $dst_hostids);
423
424		if ($same_master_item) {
425			$dst_master_items[$master_itemid] = $same_master_item;
426		}
427	}
428
429	$create_order = [];
430	$src_itemid_to_key = [];
431
432	// Calculate dependency level between items so that master items are created before dependent items.
433	foreach ($items as $itemid => $item) {
434		$dependency_level = 0;
435		$master_item = $item;
436		$src_itemid_to_key[$itemid] = $item['key_'];
437
438		while ($master_item['type'] == ITEM_TYPE_DEPENDENT) {
439			if (!array_key_exists($master_item['master_itemid'], $items)) {
440				break;
441			}
442
443			$master_item = $items[$master_item['master_itemid']];
444			++$dependency_level;
445		}
446
447		$create_order[$itemid] = $dependency_level;
448	}
449
450	asort($create_order);
451
452	$dstHosts = API::Host()->get([
453		'output' => ['hostid', 'host', 'status'],
454		'selectInterfaces' => ['interfaceid', 'type', 'main'],
455		'hostids' => $dst_hostids,
456		'preservekeys' => true,
457		'nopermissions' => true,
458		'templated_hosts' => true
459	]);
460
461	foreach ($dstHosts as $dstHost) {
462		$interfaceids = [];
463
464		foreach ($dstHost['interfaces'] as $interface) {
465			if ($interface['main'] == 1) {
466				$interfaceids[$interface['type']] = $interface['interfaceid'];
467			}
468		}
469
470		$itemkey_to_id = [];
471		$create_items = [];
472		$current_dependency = reset($create_order);
473
474		foreach ($create_order as $itemid => $dependency_level) {
475			if ($current_dependency != $dependency_level) {
476				$current_dependency = $dependency_level;
477				$created_itemids = API::Item()->create($create_items);
478
479				if (!$created_itemids) {
480					return false;
481				}
482				$created_itemids = $created_itemids['itemids'];
483
484				foreach ($create_items as $index => $created_item) {
485					$itemkey_to_id[$created_item['key_']] = $created_itemids[$index];
486				}
487
488				$create_items = [];
489			}
490
491			$item = $items[$itemid];
492
493			if ($dstHost['status'] != HOST_STATUS_TEMPLATE) {
494				$type = itemTypeInterface($item['type']);
495
496				if ($type == INTERFACE_TYPE_ANY) {
497					foreach ([INTERFACE_TYPE_AGENT, INTERFACE_TYPE_SNMP, INTERFACE_TYPE_JMX, INTERFACE_TYPE_IPMI] as $itype) {
498						if (isset($interfaceids[$itype])) {
499							$item['interfaceid'] = $interfaceids[$itype];
500							break;
501						}
502					}
503				}
504				elseif ($type !== false) {
505					if (!isset($interfaceids[$type])) {
506						error(_s('Cannot find host interface on "%1$s" for item key "%2$s".', $dstHost['host'],
507							$item['key_']
508						));
509						return false;
510					}
511					$item['interfaceid'] = $interfaceids[$type];
512				}
513			}
514			unset($item['itemid']);
515			$item['hostid'] = $dstHost['hostid'];
516			$item['applications'] = get_same_applications_for_host(
517				zbx_objectValues($item['applications'], 'applicationid'),
518				$dstHost['hostid']
519			);
520
521			if ($item['type'] == ITEM_TYPE_DEPENDENT) {
522				if (array_key_exists($item['master_itemid'], $items)) {
523					$src_item_key = $src_itemid_to_key[$item['master_itemid']];
524					$item['master_itemid'] = $itemkey_to_id[$src_item_key];
525				}
526				else {
527					$item_found = false;
528
529					if (array_key_exists($item['master_itemid'], $dst_master_items)) {
530						foreach ($dst_master_items[$item['master_itemid']] as $dst_master_item) {
531							if ($dst_master_item['hostid'] == $dstHost['hostid']) {
532								// A matching item on destination host has been found.
533
534								$item['master_itemid'] = $dst_master_item['itemid'];
535								$item_found = true;
536							}
537						}
538					}
539
540					// Master item does not exist on destination host or has not been selected for copying.
541					if (!$item_found) {
542						error(_s('Item "%1$s" has master item and cannot be copied.', $item['name']));
543
544						return false;
545					}
546				}
547			}
548			else {
549				unset($item['master_itemid']);
550			}
551
552			$create_items[] = $item;
553		}
554
555		if ($create_items && !API::Item()->create($create_items)) {
556			return false;
557		}
558	}
559
560	return true;
561}
562
563function copyItems($srcHostId, $dstHostId) {
564	$srcItems = API::Item()->get([
565		'output' => ['type', 'snmp_community', 'snmp_oid', 'name', 'key_', 'delay', 'history', 'trends', 'status',
566			'value_type', 'trapper_hosts', 'units', 'snmpv3_contextname', 'snmpv3_securityname', 'snmpv3_securitylevel',
567			'snmpv3_authprotocol', 'snmpv3_authpassphrase', 'snmpv3_privprotocol', 'snmpv3_privpassphrase',
568			'logtimefmt', 'valuemapid', 'params', 'ipmi_sensor', 'authtype', 'username', 'password', 'publickey',
569			'privatekey', 'flags', 'port', 'description', 'inventory_link', 'jmx_endpoint', 'master_itemid',
570			'templateid', 'url', 'query_fields', 'timeout', 'posts', 'status_codes', 'follow_redirects', 'post_type',
571			'http_proxy', 'headers', 'retrieve_mode', 'request_method', 'output_format', 'ssl_cert_file',
572			'ssl_key_file', 'ssl_key_password', 'verify_peer', 'verify_host', 'allow_traps'
573		],
574		'selectApplications' => ['applicationid'],
575		'selectPreprocessing' => ['type', 'params'],
576		'hostids' => $srcHostId,
577		'webitems' => true,
578		'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL],
579		'preservekeys' => true
580	]);
581	$dstHosts = API::Host()->get([
582		'output' => ['hostid', 'host', 'status'],
583		'selectInterfaces' => ['interfaceid', 'type', 'main'],
584		'hostids' => $dstHostId,
585		'preservekeys' => true,
586		'nopermissions' => true,
587		'templated_hosts' => true
588	]);
589	$dstHost = reset($dstHosts);
590
591	$create_order = [];
592	$src_itemid_to_key = [];
593	foreach ($srcItems as $itemid => $item) {
594		$dependency_level = 0;
595		$master_item = $item;
596		$src_itemid_to_key[$itemid] = $item['key_'];
597
598		while ($master_item['type'] == ITEM_TYPE_DEPENDENT) {
599			$master_item = $srcItems[$master_item['master_itemid']];
600			++$dependency_level;
601		}
602
603		$create_order[$itemid] = $dependency_level;
604	}
605	asort($create_order);
606
607	$itemkey_to_id = [];
608	$create_items = [];
609	$current_dependency = reset($create_order);
610
611	foreach ($create_order as $itemid => $dependency_level) {
612		$srcItem = $srcItems[$itemid];
613
614		// Skip creating web items. Those were created before.
615		if ($srcItem['type'] == ITEM_TYPE_HTTPTEST) {
616			continue;
617		}
618
619		if ($current_dependency != $dependency_level && $create_items) {
620			$current_dependency = $dependency_level;
621			$created_itemids = API::Item()->create($create_items);
622
623			if (!$created_itemids) {
624				return false;
625			}
626			$created_itemids = $created_itemids['itemids'];
627
628			foreach ($create_items as $index => $created_item) {
629				$itemkey_to_id[$created_item['key_']] = $created_itemids[$index];
630			}
631
632			$create_items = [];
633		}
634
635		if ($srcItem['templateid']) {
636			$srcItem = get_same_item_for_host($srcItem, $dstHost['hostid']);
637
638			if (!$srcItem) {
639				return false;
640			}
641			$itemkey_to_id[$srcItem['key_']] = $srcItem['itemid'];
642			continue;
643		}
644
645		if ($dstHost['status'] != HOST_STATUS_TEMPLATE) {
646			// find a matching interface
647			$interface = CItem::findInterfaceForItem($srcItem['type'], $dstHost['interfaces']);
648			if ($interface) {
649				$srcItem['interfaceid'] = $interface['interfaceid'];
650			}
651			// no matching interface found, throw an error
652			elseif ($interface !== false) {
653				error(_s('Cannot find host interface on "%1$s" for item key "%2$s".', $dstHost['host'], $srcItem['key_']));
654			}
655		}
656		unset($srcItem['itemid']);
657		unset($srcItem['templateid']);
658		$srcItem['hostid'] = $dstHostId;
659		$srcItem['applications'] = get_same_applications_for_host(zbx_objectValues($srcItem['applications'], 'applicationid'), $dstHostId);
660
661		if (!$srcItem['preprocessing']) {
662			unset($srcItem['preprocessing']);
663		}
664
665		if ($srcItem['type'] == ITEM_TYPE_DEPENDENT) {
666			if ($srcItems[$srcItem['master_itemid']]['type'] == ITEM_TYPE_HTTPTEST) {
667				// Web items are outside the scope and are created before regular items.
668				$web_item = get_same_item_for_host($srcItems[$srcItem['master_itemid']], $dstHost['hostid']);
669				$srcItem['master_itemid'] = $web_item['itemid'];
670			}
671			else {
672				$src_item_key = $src_itemid_to_key[$srcItem['master_itemid']];
673				$srcItem['master_itemid'] = $itemkey_to_id[$src_item_key];
674			}
675		}
676		else {
677			unset($srcItem['master_itemid']);
678		}
679
680		$create_items[] = $srcItem;
681	}
682
683	if ($create_items && !API::Item()->create($create_items)) {
684		return false;
685	}
686
687	return true;
688}
689
690/**
691 * Copy applications to a different host.
692 *
693 * @param string $source_hostid
694 * @param string $destination_hostid
695 *
696 * @return bool
697 */
698function copyApplications($source_hostid, $destination_hostid) {
699	$applications_to_create = API::Application()->get([
700		'output' => ['name'],
701		'hostids' => [$source_hostid],
702		'inherited' => false,
703		'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL]
704	]);
705
706	if (!$applications_to_create) {
707		return true;
708	}
709
710	foreach ($applications_to_create as &$application) {
711		$application['hostid'] = $destination_hostid;
712		unset($application['applicationid'], $application['templateid']);
713	}
714	unset($application);
715
716	return (bool) API::Application()->create($applications_to_create);
717}
718
719function get_item_by_itemid($itemid) {
720	$db_items = DBfetch(DBselect('SELECT i.* FROM items i WHERE i.itemid='.zbx_dbstr($itemid)));
721	if ($db_items) {
722		return $db_items;
723	}
724	error(_s('No item with itemid="%1$s".', $itemid));
725	return false;
726}
727
728/**
729 * Description:
730 * Replace items for specified host
731 *
732 * Comments:
733 * $error= true : rise Error if item doesn't exist (error generated), false: special processing (NO error generated)
734 */
735function get_same_item_for_host($item, $dest_hostids) {
736	$return_array = is_array($dest_hostids);
737	zbx_value2array($dest_hostids);
738
739	if (!is_array($item)) {
740		$itemid = $item;
741	}
742	elseif (isset($item['itemid'])) {
743		$itemid = $item['itemid'];
744	}
745
746	$same_item = null;
747	$same_items = [];
748
749	if (isset($itemid)) {
750		$db_items = DBselect(
751			'SELECT src.*'.
752			' FROM items src,items dest'.
753			' WHERE dest.itemid='.zbx_dbstr($itemid).
754				' AND src.key_=dest.key_'.
755				' AND '.dbConditionInt('src.hostid', $dest_hostids)
756		);
757		while ($db_item = DBfetch($db_items)) {
758			if (is_array($item)) {
759				$same_item = $db_item;
760				$same_items[$db_item['itemid']] = $db_item;
761			}
762			else {
763				$same_item = $db_item['itemid'];
764				$same_items[$db_item['itemid']] = $db_item['itemid'];
765			}
766		}
767		if ($return_array) {
768			return $same_items;
769		}
770		else {
771			return $same_item;
772		}
773	}
774	return false;
775}
776
777/**
778 * Get parent templates for each given item.
779 *
780 * @param array  $items                  An array of items.
781 * @param string $items[]['itemid']      ID of an item.
782 * @param string $items[]['templateid']  ID of parent template item.
783 * @param int    $flag                   Origin of the item (ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_RULE,
784 *                                       ZBX_FLAG_DISCOVERY_PROTOTYPE).
785 *
786 * @return array
787 */
788function getItemParentTemplates(array $items, $flag) {
789	$parent_itemids = [];
790	$data = [
791		'links' => [],
792		'templates' => []
793	];
794
795	foreach ($items as $item) {
796		if ($item['templateid'] != 0) {
797			$parent_itemids[$item['templateid']] = true;
798			$data['links'][$item['itemid']] = ['itemid' => $item['templateid']];
799		}
800	}
801
802	if (!$parent_itemids) {
803		return $data;
804	}
805
806	$all_parent_itemids = [];
807	$hostids = [];
808	if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
809		$lld_ruleids = [];
810	}
811
812	do {
813		if ($flag == ZBX_FLAG_DISCOVERY_RULE) {
814			$db_items = API::DiscoveryRule()->get([
815				'output' => ['itemid', 'hostid', 'templateid'],
816				'itemids' => array_keys($parent_itemids)
817			]);
818		}
819		elseif ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
820			$db_items = API::ItemPrototype()->get([
821				'output' => ['itemid', 'hostid', 'templateid'],
822				'itemids' => array_keys($parent_itemids),
823				'selectDiscoveryRule' => ['itemid']
824			]);
825		}
826		// ZBX_FLAG_DISCOVERY_NORMAL
827		else {
828			$db_items = API::Item()->get([
829				'output' => ['itemid', 'hostid', 'templateid'],
830				'itemids' => array_keys($parent_itemids),
831				'webitems' => true
832			]);
833		}
834
835		$all_parent_itemids += $parent_itemids;
836		$parent_itemids = [];
837
838		foreach ($db_items as $db_item) {
839			$data['templates'][$db_item['hostid']] = [];
840			$hostids[$db_item['itemid']] = $db_item['hostid'];
841
842			if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
843				$lld_ruleids[$db_item['itemid']] = $db_item['discoveryRule']['itemid'];
844			}
845
846			if ($db_item['templateid'] != 0) {
847				if (!array_key_exists($db_item['templateid'], $all_parent_itemids)) {
848					$parent_itemids[$db_item['templateid']] = true;
849				}
850
851				$data['links'][$db_item['itemid']] = ['itemid' => $db_item['templateid']];
852			}
853		}
854	}
855	while ($parent_itemids);
856
857	foreach ($data['links'] as &$parent_item) {
858		$parent_item['hostid'] = array_key_exists($parent_item['itemid'], $hostids)
859			? $hostids[$parent_item['itemid']]
860			: 0;
861
862		if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
863			$parent_item['lld_ruleid'] = array_key_exists($parent_item['itemid'], $lld_ruleids)
864				? $lld_ruleids[$parent_item['itemid']]
865				: 0;
866		}
867	}
868	unset($parent_item);
869
870	$db_templates = $data['templates']
871		? API::Template()->get([
872			'output' => ['name'],
873			'templateids' => array_keys($data['templates']),
874			'preservekeys' => true
875		])
876		: [];
877
878	$rw_templates = $db_templates
879		? API::Template()->get([
880			'output' => [],
881			'templateids' => array_keys($db_templates),
882			'editable' => true,
883			'preservekeys' => true
884		])
885		: [];
886
887	$data['templates'][0] = [];
888
889	foreach ($data['templates'] as $hostid => &$template) {
890		$template = array_key_exists($hostid, $db_templates)
891			? [
892				'hostid' => $hostid,
893				'name' => $db_templates[$hostid]['name'],
894				'permission' => array_key_exists($hostid, $rw_templates) ? PERM_READ_WRITE : PERM_READ
895			]
896			: [
897				'hostid' => $hostid,
898				'name' => _('Inaccessible template'),
899				'permission' => PERM_DENY
900			];
901	}
902	unset($template);
903
904	return $data;
905}
906
907/**
908 * Returns a template prefix for selected item.
909 *
910 * @param string $itemid
911 * @param array  $parent_templates  The list of the templates, prepared by getItemParentTemplates() function.
912 * @param int    $flag              Origin of the item (ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_RULE,
913 *                                  ZBX_FLAG_DISCOVERY_PROTOTYPE).
914 *
915 * @return array|null
916 */
917function makeItemTemplatePrefix($itemid, array $parent_templates, $flag) {
918	if (!array_key_exists($itemid, $parent_templates['links'])) {
919		return null;
920	}
921
922	while (array_key_exists($parent_templates['links'][$itemid]['itemid'], $parent_templates['links'])) {
923		$itemid = $parent_templates['links'][$itemid]['itemid'];
924	}
925
926	$template = $parent_templates['templates'][$parent_templates['links'][$itemid]['hostid']];
927
928	if ($template['permission'] == PERM_READ_WRITE) {
929		if ($flag == ZBX_FLAG_DISCOVERY_RULE) {
930			$url = (new CUrl('host_discovery.php'))->setArgument('hostid', $template['hostid']);
931		}
932		elseif ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
933			$url = (new CUrl('disc_prototypes.php'))
934				->setArgument('parent_discoveryid', $parent_templates['links'][$itemid]['lld_ruleid']);
935		}
936		// ZBX_FLAG_DISCOVERY_NORMAL
937		else {
938			$url = (new CUrl('items.php'))
939				->setArgument('hostid', $template['hostid'])
940				->setArgument('filter_set', 1);
941		}
942
943		$name = (new CLink(CHtml::encode($template['name']), $url))->addClass(ZBX_STYLE_LINK_ALT);
944	}
945	else {
946		$name = new CSpan(CHtml::encode($template['name']));
947	}
948
949	return [$name->addClass(ZBX_STYLE_GREY), NAME_DELIMITER];
950}
951
952/**
953 * Returns a list of item templates.
954 *
955 * @param string $itemid
956 * @param array  $parent_templates  The list of the templates, prepared by getItemParentTemplates() function.
957 * @param int    $flag              Origin of the item (ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_RULE,
958 *                                  ZBX_FLAG_DISCOVERY_PROTOTYPE).
959 *
960 * @return array
961 */
962function makeItemTemplatesHtml($itemid, array $parent_templates, $flag) {
963	$list = [];
964
965	while (array_key_exists($itemid, $parent_templates['links'])) {
966		$template = $parent_templates['templates'][$parent_templates['links'][$itemid]['hostid']];
967
968		if ($template['permission'] == PERM_READ_WRITE) {
969			if ($flag == ZBX_FLAG_DISCOVERY_RULE) {
970				$url = (new CUrl('host_discovery.php'))
971					->setArgument('form', 'update')
972					->setArgument('itemid', $parent_templates['links'][$itemid]['itemid']);
973			}
974			elseif ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
975				$url = (new CUrl('disc_prototypes.php'))
976					->setArgument('form', 'update')
977					->setArgument('itemid', $parent_templates['links'][$itemid]['itemid'])
978					->setArgument('parent_discoveryid', $parent_templates['links'][$itemid]['lld_ruleid']);
979			}
980			// ZBX_FLAG_DISCOVERY_NORMAL
981			else {
982				$url = (new CUrl('items.php'))
983					->setArgument('form', 'update')
984					->setArgument('itemid', $parent_templates['links'][$itemid]['itemid']);
985			}
986
987			$name = new CLink(CHtml::encode($template['name']), $url);
988		}
989		else {
990			$name = (new CSpan(CHtml::encode($template['name'])))->addClass(ZBX_STYLE_GREY);
991		}
992
993		array_unshift($list, $name, '&nbsp;&rArr;&nbsp;');
994
995		$itemid = $parent_templates['links'][$itemid]['itemid'];
996	}
997
998	if ($list) {
999		array_pop($list);
1000	}
1001
1002	return $list;
1003}
1004
1005/**
1006 * Retrieve overview table object for items.
1007 *
1008 * @param array  $groupids
1009 * @param string $application      IDs of applications to filter items by.
1010 * @param int    $viewMode
1011 * @param int    $show_suppressed  Whether to show suppressed problems.
1012 *
1013 * @return CTableInfo
1014 */
1015
1016function getItemsDataOverview(array $groupids, $application, $viewMode,
1017		$show_suppressed = ZBX_PROBLEM_SUPPRESSED_TRUE) {
1018	// application filter
1019	if ($application !== '') {
1020		$applicationids = array_keys(API::Application()->get([
1021			'output' => [],
1022			'groupids' => $groupids ? $groupids : null,
1023			'search' => ['name' => $application],
1024			'preservekeys' => true
1025		]));
1026		$groupids = [];
1027	}
1028	else {
1029		$applicationids = null;
1030	}
1031
1032	$db_items = API::Item()->get([
1033		'output' => ['itemid', 'hostid', 'key_', 'name', 'value_type', 'units', 'valuemapid'],
1034		'selectHosts' => ['name'],
1035		'groupids' => $groupids ? $groupids : null,
1036		'applicationids' => $applicationids,
1037		'monitored' => true,
1038		'webitems' => true,
1039		'preservekeys' => true
1040	]);
1041
1042	$db_triggers = getTriggersWithActualSeverity([
1043		'output' => ['triggerid', 'priority', 'value'],
1044		'selectItems' => ['itemid'],
1045		'groupids' => $groupids ? $groupids : null,
1046		'applicationids' => $applicationids,
1047		'monitored' => true,
1048		'preservekeys' => true
1049	], ['show_suppressed' => $show_suppressed]);
1050
1051	foreach ($db_triggers as $db_trigger) {
1052		foreach ($db_trigger['items'] as $item) {
1053			if (array_key_exists($item['itemid'], $db_items)) {
1054				$db_item = &$db_items[$item['itemid']];
1055
1056				// a little tricky check for attempt to overwrite active trigger (value=1) with
1057				// inactive or active trigger with lower priority.
1058				if (!array_key_exists('triggerid', $db_item)
1059						|| ($db_item['value'] == TRIGGER_VALUE_FALSE && $db_trigger['value'] == TRIGGER_VALUE_TRUE)
1060						|| (($db_item['value'] == TRIGGER_VALUE_FALSE || $db_trigger['value'] == TRIGGER_VALUE_TRUE)
1061							&& $db_item['priority'] < $db_trigger['priority'])) {
1062					$db_item['triggerid'] = $db_trigger['triggerid'];
1063					$db_item['priority'] = $db_trigger['priority'];
1064					$db_item['value'] = $db_trigger['value'];
1065				}
1066
1067				$db_item['acknowledged'] = $db_trigger['problem']['acknowledged'];
1068
1069				unset($db_item);
1070			}
1071		}
1072	}
1073
1074	$db_items = CMacrosResolverHelper::resolveItemNames($db_items);
1075
1076	CArrayHelper::sort($db_items, [
1077		['field' => 'name_expanded', 'order' => ZBX_SORT_UP],
1078		['field' => 'itemid', 'order' => ZBX_SORT_UP]
1079	]);
1080
1081	// fetch latest values
1082	$history = Manager::History()->getLastValues(zbx_toHash($db_items, 'itemid'), 1, ZBX_HISTORY_PERIOD);
1083
1084	// fetch data for the host JS menu
1085	$hosts = API::Host()->get([
1086		'output' => ['name', 'hostid'],
1087		'monitored_hosts' => true,
1088		'groupids' => $groupids ? $groupids : null,
1089		'applicationids' => $applicationids,
1090		'with_monitored_items' => true,
1091		'preservekeys' => true
1092	]);
1093
1094	$items = [];
1095	$item_counter = [];
1096	$host_items = [];
1097	$host_names = [];
1098
1099	foreach ($db_items as $db_item) {
1100		$item_name = $db_item['name_expanded'];
1101		$host_name = $db_item['hosts'][0]['name'];
1102		$host_names[$db_item['hostid']] = $host_name;
1103
1104		if (!array_key_exists($host_name, $item_counter)) {
1105			$item_counter[$host_name] = [];
1106		}
1107
1108		if (!array_key_exists($item_name, $item_counter[$host_name])) {
1109			$item_counter[$host_name][$item_name] = 0;
1110		}
1111
1112		if (!array_key_exists($item_name, $host_items) || !array_key_exists($host_name, $host_items[$item_name])) {
1113			$host_items[$item_name][$host_name] = [];
1114		}
1115
1116		if (!array_key_exists($db_item['itemid'], $host_items[$item_name][$host_name])) {
1117			if (array_key_exists($db_item['itemid'], $host_items[$item_name][$host_name])) {
1118				$item_place = $host_items[$item_name][$host_name][$db_item['itemid']]['item_place'];
1119			}
1120			else {
1121				$item_place = $item_counter[$host_name][$item_name];
1122				$item_counter[$host_name][$item_name]++;
1123			}
1124
1125			$item = [
1126				'itemid' => $db_item['itemid'],
1127				'value_type' => $db_item['value_type'],
1128				'value' => isset($history[$db_item['itemid']]) ? $history[$db_item['itemid']][0]['value'] : null,
1129				'units' => $db_item['units'],
1130				'valuemapid' => $db_item['valuemapid'],
1131				'item_place' => $item_place,
1132				'acknowledged' => array_key_exists('acknowledged', $db_item) ? $db_item['acknowledged'] : 0
1133			];
1134
1135			if (array_key_exists('triggerid', $db_item)) {
1136				$item += [
1137					'triggerid' => $db_item['triggerid'],
1138					'severity' => $db_item['priority'],
1139					'tr_value' => $db_item['value']
1140				];
1141			}
1142			else {
1143				$item += [
1144					'triggerid' => null,
1145					'severity' => null,
1146					'tr_value' => null
1147				];
1148			}
1149
1150			$items[$item_name][$item_place][$host_name] = $item;
1151
1152			$host_items[$item_name][$host_name][$db_item['itemid']] = $items[$item_name][$item_place][$host_name];
1153		}
1154	}
1155
1156	$table = (new CTableInfo())->setHeadingColumn(0);
1157	if (!$host_names) {
1158		return $table;
1159	}
1160	$table->makeVerticalRotation();
1161
1162	order_result($host_names);
1163
1164	if ($viewMode == STYLE_TOP) {
1165		$header = [_('Items')];
1166		foreach ($host_names as $host_name) {
1167			$header[] = (new CColHeader($host_name))
1168				->addClass('vertical_rotation')
1169				->setTitle($host_name);
1170		}
1171		$table->setHeader($header);
1172
1173		foreach ($items as $item_name => $item_data) {
1174			foreach ($item_data as $ithosts) {
1175				$tableRow = [(new CColHeader($item_name))->addClass(ZBX_STYLE_NOWRAP)];
1176				foreach ($host_names as $host_name) {
1177					$tableRow = getItemDataOverviewCells($tableRow, $ithosts, $host_name);
1178				}
1179				$table->addRow($tableRow);
1180			}
1181		}
1182	}
1183	else {
1184		$header = [_('Hosts')];
1185		foreach ($items as $item_name => $item_data) {
1186			foreach ($item_data as $ithosts) {
1187				$header[] = (new CColHeader($item_name))
1188					->addClass('vertical_rotation')
1189					->setTitle($item_name);
1190			}
1191		}
1192		$table->setHeader($header);
1193
1194		foreach ($host_names as $hostId => $host_name) {
1195			$host = $hosts[$hostId];
1196
1197			$name = (new CLinkAction($host['name']))->setMenuPopup(CMenuPopupHelper::getHost($hostId));
1198
1199			$tableRow = [(new CColHeader($name))->addClass(ZBX_STYLE_NOWRAP)];
1200			foreach ($items as $item_data) {
1201				foreach ($item_data as $ithosts) {
1202					$tableRow = getItemDataOverviewCells($tableRow, $ithosts, $host_name);
1203				}
1204			}
1205			$table->addRow($tableRow);
1206		}
1207	}
1208
1209	return $table;
1210}
1211
1212function getItemDataOverviewCells($tableRow, $ithosts, $hostName) {
1213	$ack = null;
1214	$css = '';
1215	$value = UNKNOWN_VALUE;
1216
1217	if (isset($ithosts[$hostName])) {
1218		$item = $ithosts[$hostName];
1219
1220		if ($item['tr_value'] == TRIGGER_VALUE_TRUE) {
1221			$css = getSeverityStyle($item['severity']);
1222			$ack = ($item['acknowledged'] == 1) ? [' ', (new CSpan())->addClass(ZBX_STYLE_ICON_ACKN)] : null;
1223		}
1224
1225		if ($item['value'] !== null) {
1226			$value = formatHistoryValue($item['value'], $item);
1227		}
1228	}
1229
1230	if ($value != UNKNOWN_VALUE) {
1231		$value = $value;
1232	}
1233
1234	$column = (new CCol([$value, $ack]))->addClass($css);
1235
1236	if (isset($ithosts[$hostName])) {
1237		$column
1238			->setMenuPopup(CMenuPopupHelper::getHistory($item['itemid']))
1239			->addClass(ZBX_STYLE_CURSOR_POINTER)
1240			->addClass(ZBX_STYLE_NOWRAP);
1241	}
1242
1243	$tableRow[] = $column;
1244
1245	return $tableRow;
1246}
1247
1248/**
1249 * Get same application IDs on destination host and return array with keys as source application IDs
1250 * and values as destination application IDs.
1251 *
1252 * Comments: !!! Don't forget sync code with C !!!
1253 *
1254 * @param array  $applicationIds
1255 * @param string $hostId
1256 *
1257 * @return array
1258 */
1259function get_same_applications_for_host(array $applicationIds, $hostId) {
1260	$applications = [];
1261
1262	$dbApplications = DBselect(
1263		'SELECT a1.applicationid AS dstappid,a2.applicationid AS srcappid'.
1264		' FROM applications a1,applications a2'.
1265		' WHERE a1.name=a2.name'.
1266			' AND a1.hostid='.zbx_dbstr($hostId).
1267			' AND '.dbConditionInt('a2.applicationid', $applicationIds)
1268	);
1269
1270	while ($dbApplication = DBfetch($dbApplications)) {
1271		$applications[$dbApplication['srcappid']] = $dbApplication['dstappid'];
1272	}
1273
1274	return $applications;
1275}
1276
1277/******************************************************************************
1278 *                                                                            *
1279 * Comments: !!! Don't forget sync code with C !!!                            *
1280 *                                                                            *
1281 ******************************************************************************/
1282function get_applications_by_itemid($itemids, $field = 'applicationid') {
1283	zbx_value2array($itemids);
1284	$result = [];
1285	$db_applications = DBselect(
1286		'SELECT DISTINCT app.'.$field.' AS result'.
1287		' FROM applications app,items_applications ia'.
1288		' WHERE app.applicationid=ia.applicationid'.
1289			' AND '.dbConditionInt('ia.itemid', $itemids)
1290	);
1291	while ($db_application = DBfetch($db_applications)) {
1292		array_push($result, $db_application['result']);
1293	}
1294
1295	return $result;
1296}
1297
1298/**
1299 * Format history value.
1300 * First format the value according to the configuration of the item. Then apply the value mapping to the formatted (!)
1301 * value.
1302 *
1303 * @param mixed     $value
1304 * @param array     $item
1305 * @param int       $item['value_type']     type of the value: ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64, ...
1306 * @param string    $item['units']          units of item
1307 * @param int       $item['valuemapid']     id of mapping set of values
1308 * @param bool      $trim
1309 *
1310 * @return string
1311 */
1312function formatHistoryValue($value, array $item, $trim = true) {
1313	$mapping = false;
1314
1315	// format value
1316	if ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT || $item['value_type'] == ITEM_VALUE_TYPE_UINT64) {
1317		$value = convert_units([
1318				'value' => $value,
1319				'units' => $item['units']
1320		]);
1321	}
1322	elseif ($item['value_type'] != ITEM_VALUE_TYPE_STR
1323		&& $item['value_type'] != ITEM_VALUE_TYPE_TEXT
1324		&& $item['value_type'] != ITEM_VALUE_TYPE_LOG) {
1325
1326		$value = _('Unknown value type');
1327	}
1328
1329	// apply value mapping
1330	switch ($item['value_type']) {
1331		case ITEM_VALUE_TYPE_STR:
1332			$mapping = getMappedValue($value, $item['valuemapid']);
1333		// break; is not missing here
1334		case ITEM_VALUE_TYPE_TEXT:
1335		case ITEM_VALUE_TYPE_LOG:
1336			if ($trim && mb_strlen($value) > 20) {
1337				$value = mb_substr($value, 0, 20).'...';
1338			}
1339
1340			if ($mapping !== false) {
1341				$value = $mapping.' ('.$value.')';
1342			}
1343			break;
1344		default:
1345			$value = applyValueMap($value, $item['valuemapid']);
1346	}
1347
1348	return $value;
1349}
1350
1351/**
1352 * Retrieves from DB historical data for items and applies functional calculations.
1353 * If fails for some reason, returns UNRESOLVED_MACRO_STRING.
1354 *
1355 * @param array		$item
1356 * @param string	$item['value_type']	type of item, allowed: ITEM_VALUE_TYPE_FLOAT and ITEM_VALUE_TYPE_UINT64
1357 * @param string	$item['itemid']		ID of item
1358 * @param string	$item['units']		units of item
1359 * @param string	$function			function to apply to time period from param, allowed: min, max and avg
1360 * @param string	$parameter			formatted parameter for function, example: "2w" meaning 2 weeks
1361 *
1362 * @return string item functional value from history
1363 */
1364function getItemFunctionalValue($item, $function, $parameter) {
1365	// check whether function is allowed
1366	if (!in_array($function, ['min', 'max', 'avg']) || $parameter === '') {
1367		return UNRESOLVED_MACRO_STRING;
1368	}
1369
1370	$parameter = convertFunctionValue($parameter);
1371
1372	if (bccomp($parameter, 0) == 0) {
1373		return UNRESOLVED_MACRO_STRING;
1374	}
1375
1376	// allowed item types for min, max and avg function
1377	$history_tables = [ITEM_VALUE_TYPE_FLOAT => 'history', ITEM_VALUE_TYPE_UINT64 => 'history_uint'];
1378
1379	if (!array_key_exists($item['value_type'], $history_tables)) {
1380		return UNRESOLVED_MACRO_STRING;
1381	}
1382	else {
1383		$result = Manager::History()->getAggregatedValue($item, $function, (time() - $parameter));
1384
1385		if ($result !== null) {
1386			return convert_units(['value' => $result, 'units' => $item['units']]);
1387		}
1388		// no data in history
1389		else {
1390			return UNRESOLVED_MACRO_STRING;
1391		}
1392	}
1393}
1394
1395/**
1396 * Check if current time is within the given period.
1397 *
1398 * @param string $period	time period format: "wd[-wd2],hh:mm-hh:mm"
1399 * @param int $now			current timestamp
1400 *
1401 * @return bool		true - within period, false - out of period
1402 */
1403function checkTimePeriod($period, $now) {
1404	if (sscanf($period, '%d-%d,%d:%d-%d:%d', $d1, $d2, $h1, $m1, $h2, $m2) != 6) {
1405		if (sscanf($period, '%d,%d:%d-%d:%d', $d1, $h1, $m1, $h2, $m2) != 5) {
1406			// delay period format is wrong - skip
1407			return false;
1408		}
1409		$d2 = $d1;
1410	}
1411
1412	$tm = localtime($now, true);
1413	$day = ($tm['tm_wday'] == 0) ? 7 : $tm['tm_wday'];
1414	$sec = SEC_PER_HOUR * $tm['tm_hour'] + SEC_PER_MIN * $tm['tm_min'] + $tm['tm_sec'];
1415
1416	$sec1 = SEC_PER_HOUR * $h1 + SEC_PER_MIN * $m1;
1417	$sec2 = SEC_PER_HOUR * $h2 + SEC_PER_MIN * $m2;
1418
1419	return $d1 <= $day && $day <= $d2 && $sec1 <= $sec && $sec < $sec2;
1420}
1421
1422/**
1423 * Get item minimum delay.
1424 *
1425 * @param string $delay
1426 * @param array $flexible_intervals
1427 *
1428 * @return string
1429 */
1430function getItemDelay($delay, array $flexible_intervals) {
1431	$delay = timeUnitToSeconds($delay);
1432
1433	if ($delay != 0 || !$flexible_intervals) {
1434		return $delay;
1435	}
1436
1437	$min_delay = SEC_PER_YEAR;
1438
1439	foreach ($flexible_intervals as $flexible_interval) {
1440		$flexible_interval_parts = explode('/', $flexible_interval);
1441		$flexible_delay = timeUnitToSeconds($flexible_interval_parts[0]);
1442
1443		$min_delay = min($min_delay, $flexible_delay);
1444	}
1445
1446	return $min_delay;
1447}
1448
1449/**
1450 * Return delay value that is currently applicable
1451 *
1452 * @param int $delay					default delay
1453 * @param array $flexible_intervals		array of intervals in format: "d/wd[-wd2],hh:mm-hh:mm"
1454 * @param int $now						current timestamp
1455 *
1456 * @return int							delay for a current timestamp
1457 */
1458function getCurrentDelay($delay, array $flexible_intervals, $now) {
1459	if (!$flexible_intervals) {
1460		return $delay;
1461	}
1462
1463	$current_delay = -1;
1464
1465	foreach ($flexible_intervals as $flexible_interval) {
1466		list($flexible_delay, $flexible_period) = explode('/', $flexible_interval);
1467		$flexible_delay = (int) $flexible_delay;
1468
1469		if (($current_delay == -1 || $flexible_delay < $current_delay) && checkTimePeriod($flexible_period, $now)) {
1470			$current_delay = $flexible_delay;
1471		}
1472	}
1473
1474	if ($current_delay == -1) {
1475		return $delay;
1476	}
1477
1478	return $current_delay;
1479}
1480
1481/**
1482 * Return time of next flexible interval
1483 *
1484 * @param array $flexible_intervals  array of intervals in format: "d/wd[-wd2],hh:mm-hh:mm"
1485 * @param int $now                   current timestamp
1486 * @param int $next_interval          timestamp of a next interval
1487 *
1488 * @return bool                      false if no flexible intervals defined
1489 */
1490function getNextDelayInterval(array $flexible_intervals, $now, &$next_interval) {
1491	if (!$flexible_intervals) {
1492		return false;
1493	}
1494
1495	$next = 0;
1496	$tm = localtime($now, true);
1497	$day = ($tm['tm_wday'] == 0) ? 7 : $tm['tm_wday'];
1498	$sec = SEC_PER_HOUR * $tm['tm_hour'] + SEC_PER_MIN * $tm['tm_min'] + $tm['tm_sec'];
1499
1500	foreach ($flexible_intervals as $flexible_interval) {
1501		$flexible_interval_parts = explode('/', $flexible_interval);
1502
1503		if (sscanf($flexible_interval_parts[1], '%d-%d,%d:%d-%d:%d', $d1, $d2, $h1, $m1, $h2, $m2) != 6) {
1504			if (sscanf($flexible_interval_parts[1], '%d,%d:%d-%d:%d', $d1, $h1, $m1, $h2, $m2) != 5) {
1505				continue;
1506			}
1507			$d2 = $d1;
1508		}
1509
1510		$sec1 = SEC_PER_HOUR * $h1 + SEC_PER_MIN * $m1;
1511		$sec2 = SEC_PER_HOUR * $h2 + SEC_PER_MIN * $m2;
1512
1513		// current period
1514		if ($d1 <= $day && $day <= $d2 && $sec1 <= $sec && $sec < $sec2) {
1515			if ($next == 0 || $next > $now - $sec + $sec2) {
1516				// the next second after the current interval's upper bound
1517				$next = $now - $sec + $sec2;
1518			}
1519		}
1520		// will be active today
1521		elseif ($d1 <= $day && $d2 >= $day && $sec < $sec1) {
1522			if ($next == 0 || $next > $now - $sec + $sec1) {
1523				$next = $now - $sec + $sec1;
1524			}
1525		}
1526		else {
1527			$nextDay = ($day + 1 <= 7) ? $day + 1 : 1;
1528
1529			// will be active tomorrow
1530			if ($d1 <= $nextDay && $nextDay <= $d2) {
1531				if ($next == 0 || $next > $now - $sec + SEC_PER_DAY + $sec1) {
1532					$next = $now - $sec + SEC_PER_DAY + $sec1;
1533				}
1534			}
1535			// later in the future
1536			else {
1537				$dayDiff = -1;
1538
1539				if ($day < $d1) {
1540					$dayDiff = $d1 - $day;
1541				}
1542				if ($day >= $d2) {
1543					$dayDiff = ($d1 + 7) - $day;
1544				}
1545				if ($d1 <= $day && $day < $d2) {
1546					// should never happen, could not deduce day difference
1547					$dayDiff = -1;
1548				}
1549				if ($dayDiff != -1 && ($next == 0 || $next > $now - $sec + SEC_PER_DAY * $dayDiff + $sec1)) {
1550					$next = $now - $sec + SEC_PER_DAY * $dayDiff + $sec1;
1551				}
1552			}
1553		}
1554	}
1555
1556	if ($next != 0) {
1557		$next_interval = $next;
1558	}
1559
1560	return ($next != 0);
1561}
1562
1563/**
1564 * Calculate nextcheck timestamp for an item using flexible intervals.
1565 *
1566 * the parameter $flexible_intervals is an array if strings that are in the following format:
1567 *
1568 *           +------------[;]<----------+
1569 *           |                          |
1570 *         ->+-[d/wd[-wd2],hh:mm-hh:mm]-+
1571 *
1572 *         d       - delay (0-n)
1573 *         wd, wd2 - day of week (1-7)
1574 *         hh      - hours (0-24)
1575 *         mm      - minutes (0-59)
1576 *
1577 * @param int $seed						seed value applied to delay to spread item checks over the delay period
1578 * @param string $delay					default delay, can be overridden
1579 * @param array $flexible_intervals		array of flexible intervals
1580 * @param int $now						current timestamp
1581 *
1582 * @return int
1583 */
1584function calculateItemNextCheck($seed, $delay, $flexible_intervals, $now) {
1585	/*
1586	 * Try to find the nearest 'nextcheck' value with condition 'now' < 'nextcheck' < 'now' + SEC_PER_YEAR
1587	 * If it is not possible to check the item within a year, fail.
1588	 */
1589
1590	$t = $now;
1591	$tMax = $now + SEC_PER_YEAR;
1592	$try = 0;
1593
1594	while ($t < $tMax) {
1595		// Calculate 'nextcheck' value for the current interval.
1596		$currentDelay = getCurrentDelay($delay, $flexible_intervals, $t);
1597
1598		if ($currentDelay != 0) {
1599			$nextCheck = $currentDelay * floor($t / $currentDelay) + ($seed % $currentDelay);
1600
1601			if ($try == 0) {
1602				while ($nextCheck <= $t) {
1603					$nextCheck += $currentDelay;
1604				}
1605			}
1606			else {
1607				while ($nextCheck < $t) {
1608					$nextCheck += $currentDelay;
1609				}
1610			}
1611		}
1612		else {
1613			$nextCheck = ZBX_JAN_2038;
1614		}
1615
1616		/*
1617		 * Is 'nextcheck' < end of the current interval and the end of the current interval
1618		 * is the beginning of the next interval - 1.
1619		 */
1620		if (getNextDelayInterval($flexible_intervals, $t, $nextInterval) && $nextCheck >= $nextInterval) {
1621			// 'nextcheck' is beyond the current interval.
1622			$t = $nextInterval;
1623			$try++;
1624		}
1625		else {
1626			break;
1627		}
1628	}
1629
1630	return $nextCheck;
1631}
1632
1633/*
1634 * Description:
1635 *	Function returns true if http items exists in the $items array.
1636 *	The array should contain a field 'type'
1637 */
1638function httpItemExists($items) {
1639	foreach ($items as $item) {
1640		if ($item['type'] == ITEM_TYPE_HTTPTEST) {
1641			return true;
1642		}
1643	}
1644	return false;
1645}
1646
1647function getParamFieldNameByType($itemType) {
1648	switch ($itemType) {
1649		case ITEM_TYPE_SSH:
1650		case ITEM_TYPE_TELNET:
1651		case ITEM_TYPE_JMX:
1652			return 'params_es';
1653		case ITEM_TYPE_DB_MONITOR:
1654			return 'params_ap';
1655		case ITEM_TYPE_CALCULATED:
1656			return 'params_f';
1657		default:
1658			return 'params';
1659	}
1660}
1661
1662function getParamFieldLabelByType($itemType) {
1663	switch ($itemType) {
1664		case ITEM_TYPE_SSH:
1665		case ITEM_TYPE_TELNET:
1666		case ITEM_TYPE_JMX:
1667			return _('Executed script');
1668		case ITEM_TYPE_DB_MONITOR:
1669			return _('SQL query');
1670		case ITEM_TYPE_CALCULATED:
1671			return _('Formula');
1672		default:
1673			return 'params';
1674	}
1675}
1676
1677/**
1678 * Get either one or all item preprocessing types. If grouped set to true, returns group labels. Returns empty string if
1679 * no specific type is found.
1680 *
1681 * Usage examples:
1682 *    - get_preprocessing_types()              Returns array as defined.
1683 *    - get_preprocessing_types(4)             Returns string: 'Trim'.
1684 *    - get_preprocessing_types(<wrong type>)  Returns an empty string: ''.
1685 *    - get_preprocessing_types(null, false)   Returns subarrays in one array maintaining index:
1686 *                                               [5] => Regular expression
1687 *                                               [4] => Trim
1688 *                                               [2] => Right trim
1689 *                                               [3] => Left trim
1690 *                                               [11] => XML XPath
1691 *                                               [12] => JSON Path
1692 *                                               [1] => Custom multiplier
1693 *                                               [9] => Simple change
1694 *                                               [10] => Speed per second
1695 *                                               [6] => Boolean to decimal
1696 *                                               [7] => Octal to decimal
1697 *                                               [8] => Hexadecimal to decimal
1698 *
1699 * @param int  $type     Item preprocessing type.
1700 * @param bool $grouped  Group label flag.
1701 *
1702 * @return mixed
1703 */
1704function get_preprocessing_types($type = null, $grouped = true) {
1705	$groups = [
1706		[
1707			'label' => _('Text'),
1708			'types' => [
1709				ZBX_PREPROC_REGSUB => _('Regular expression'),
1710				ZBX_PREPROC_TRIM => _('Trim'),
1711				ZBX_PREPROC_RTRIM => _('Right trim'),
1712				ZBX_PREPROC_LTRIM => _('Left trim')
1713			]
1714		],
1715		[
1716			'label' => _('Structured data'),
1717			'types' => [
1718				ZBX_PREPROC_XPATH => _('XML XPath'),
1719				ZBX_PREPROC_JSONPATH => _('JSON Path')
1720			]
1721		],
1722		[
1723			'label' => _('Arithmetic'),
1724			'types' => [
1725				ZBX_PREPROC_MULTIPLIER => _('Custom multiplier')
1726			]
1727		],
1728		[
1729			'label' => _x('Change', 'noun'),
1730			'types' => [
1731				ZBX_PREPROC_DELTA_VALUE => _('Simple change'),
1732				ZBX_PREPROC_DELTA_SPEED => _('Change per second')
1733			]
1734		],
1735		[
1736			'label' => _('Numeral systems'),
1737			'types' => [
1738				ZBX_PREPROC_BOOL2DEC => _('Boolean to decimal'),
1739				ZBX_PREPROC_OCT2DEC => _('Octal to decimal'),
1740				ZBX_PREPROC_HEX2DEC => _('Hexadecimal to decimal')
1741			]
1742		]
1743	];
1744
1745	if ($type !== null) {
1746		foreach ($groups as $group) {
1747			if (array_key_exists($type, $group['types'])) {
1748				return $group['types'][$type];
1749			}
1750		}
1751
1752		return '';
1753	}
1754	elseif ($grouped) {
1755		return $groups;
1756	}
1757	else {
1758		$types = [];
1759
1760		foreach ($groups as $group) {
1761			$types += $group['types'];
1762		}
1763
1764		return $types;
1765	}
1766}
1767
1768/*
1769 * Quoting $param if it contain special characters.
1770 *
1771 * @param string $param
1772 * @param bool   $forced
1773 *
1774 * @return string
1775 */
1776function quoteItemKeyParam($param, $forced = false) {
1777	if (!$forced) {
1778		if (!isset($param[0]) || ($param[0] != '"' && false === strpbrk($param, ',]'))) {
1779			return $param;
1780		}
1781	}
1782
1783	return '"'.str_replace('"', '\\"', $param).'"';
1784}
1785
1786/**
1787 * Expands item name and for dependent item master item name.
1788 *
1789 * @param array  $items        Array of items.
1790 * @param string $data_source  'items' or 'itemprototypes'.
1791 *
1792 * @return array
1793 */
1794function expandItemNamesWithMasterItems($items, $data_source) {
1795	$items = CMacrosResolverHelper::resolveItemNames($items);
1796	$itemids = [];
1797	$master_itemids = [];
1798
1799	foreach ($items as $item_index => &$item) {
1800		if ($item['type'] == ITEM_TYPE_DEPENDENT) {
1801			$master_itemids[$item['master_itemid']] = true;
1802		}
1803
1804		// The "source" is required to tell the frontend where the link should point at - item or item prototype.
1805		$item['source'] = $data_source;
1806		$itemids[$item_index] = $item['itemid'];
1807	}
1808	unset($item);
1809
1810	$master_itemids = array_diff(array_keys($master_itemids), $itemids);
1811
1812	if ($master_itemids) {
1813		$options = [
1814			'output' => ['itemid', 'type', 'hostid', 'name', 'key_'],
1815			'itemids' => $master_itemids,
1816			'editable' => true,
1817			'preservekeys' => true
1818		];
1819		$master_items = API::Item()->get($options + ['webitems' => true]);
1820
1821		foreach ($master_items as &$master_item) {
1822			$master_item['source'] = 'items';
1823		}
1824		unset($master_item);
1825
1826		$master_item_prototypes = API::ItemPrototype()->get($options);
1827
1828		foreach ($master_item_prototypes as &$master_item_prototype) {
1829			$master_item_prototype['source'] = 'itemprototypes';
1830		}
1831		unset($master_item_prototype);
1832
1833		$master_items = CMacrosResolverHelper::resolveItemNames($master_items + $master_item_prototypes);
1834	}
1835
1836	foreach ($items as &$item) {
1837		if ($item['type'] == ITEM_TYPE_DEPENDENT) {
1838			$master_itemid = $item['master_itemid'];
1839			$items_index = array_search($master_itemid, $itemids);
1840
1841			$item['master_item'] = [
1842				'itemid' => $master_itemid,
1843				'name_expanded' => ($items_index === false)
1844					? $master_items[$master_itemid]['name_expanded']
1845					: $items[$items_index]['name_expanded'],
1846				'type' => ($items_index === false)
1847					? $master_items[$master_itemid]['type']
1848					: $items[$items_index]['type'],
1849				'source' => ($items_index === false)
1850					? $master_items[$master_itemid]['source']
1851					: $items[$items_index]['source']
1852			];
1853		}
1854	}
1855	unset($item);
1856
1857	return $items;
1858}
1859
1860/**
1861 * Returns an array of allowed item types for "Check now" functionality.
1862 *
1863 * @return array
1864 */
1865function checkNowAllowedTypes() {
1866	return [
1867		ITEM_TYPE_ZABBIX,
1868		ITEM_TYPE_SNMPV1,
1869		ITEM_TYPE_SIMPLE,
1870		ITEM_TYPE_SNMPV2C,
1871		ITEM_TYPE_INTERNAL,
1872		ITEM_TYPE_SNMPV3,
1873		ITEM_TYPE_AGGREGATE,
1874		ITEM_TYPE_EXTERNAL,
1875		ITEM_TYPE_DB_MONITOR,
1876		ITEM_TYPE_IPMI,
1877		ITEM_TYPE_SSH,
1878		ITEM_TYPE_TELNET,
1879		ITEM_TYPE_CALCULATED,
1880		ITEM_TYPE_JMX,
1881		ITEM_TYPE_HTTPAGENT
1882	];
1883}
1884