_('Zabbix agent'), ITEM_TYPE_ZABBIX_ACTIVE => _('Zabbix agent (active)'), ITEM_TYPE_SIMPLE => _('Simple check'), ITEM_TYPE_SNMP => _('SNMP agent'), ITEM_TYPE_SNMPTRAP => _('SNMP trap'), ITEM_TYPE_INTERNAL => _('Zabbix internal'), ITEM_TYPE_TRAPPER => _('Zabbix trapper'), ITEM_TYPE_EXTERNAL => _('External check'), ITEM_TYPE_DB_MONITOR => _('Database monitor'), ITEM_TYPE_HTTPAGENT => _('HTTP agent'), ITEM_TYPE_IPMI => _('IPMI agent'), ITEM_TYPE_SSH => _('SSH agent'), ITEM_TYPE_TELNET => _('TELNET agent'), ITEM_TYPE_JMX => _('JMX agent'), ITEM_TYPE_CALCULATED => _('Calculated'), ITEM_TYPE_HTTPTEST => _('Web monitoring'), ITEM_TYPE_DEPENDENT => _('Dependent item'), ITEM_TYPE_SCRIPT => _('Script') ]; if ($type === null) { return $types; } return array_key_exists($type, $types) ? $types[$type] : _('Unknown'); } /** * Returns label for value type. * * @param int $value_type * * @return string */ function itemValueTypeString($value_type): string { switch ($value_type) { case ITEM_VALUE_TYPE_UINT64: return _('Numeric (unsigned)'); case ITEM_VALUE_TYPE_FLOAT: return _('Numeric (float)'); case ITEM_VALUE_TYPE_STR: return _('Character'); case ITEM_VALUE_TYPE_LOG: return _('Log'); case ITEM_VALUE_TYPE_TEXT: return _('Text'); case ITEM_VALUE_TYPE_BINARY: return _('Binary'); } return _('Unknown'); } function item_status2str($type = null) { if (is_null($type)) { return [ITEM_STATUS_ACTIVE => _('Enabled'), ITEM_STATUS_DISABLED => _('Disabled')]; } return ($type == ITEM_STATUS_ACTIVE) ? _('Enabled') : _('Disabled'); } /** * Returns the names of supported item states. * * If the $state parameter is passed, returns the name of the specific state, otherwise - returns an array of all * supported states. * * @param string $state * * @return array|string */ function itemState($state = null) { $states = [ ITEM_STATE_NORMAL => _('Normal'), ITEM_STATE_NOTSUPPORTED => _('Not supported') ]; if ($state === null) { return $states; } elseif (isset($states[$state])) { return $states[$state]; } else { return _('Unknown'); } } /** * Returns the text indicating the items status and state. If the $state parameter is not given, only the status of * the item will be taken into account. * * @param int $status * @param int $state * * @return string */ function itemIndicator($status, $state = null) { if ($status == ITEM_STATUS_ACTIVE) { return ($state == ITEM_STATE_NOTSUPPORTED) ? _('Not supported') : _('Enabled'); } return _('Disabled'); } /** * Returns the CSS class for the items status and state indicator. If the $state parameter is not given, only the status of * the item will be taken into account. * * @param int $status * @param int $state * * @return string */ function itemIndicatorStyle($status, $state = null) { if ($status == ITEM_STATUS_ACTIVE) { return ($state == ITEM_STATE_NOTSUPPORTED) ? ZBX_STYLE_GREY : ZBX_STYLE_GREEN; } return ZBX_STYLE_RED; } /** * Order items by keep history. * * @param array $items * @param string $items['history'] * @param string $sortorder */ function orderItemsByHistory(array &$items, $sortorder){ $simple_interval_parser = new CSimpleIntervalParser(); foreach ($items as &$item) { $item['history_sort'] = ($simple_interval_parser->parse($item['history']) == CParser::PARSE_SUCCESS) ? timeUnitToSeconds($item['history']) : $item['history']; } unset($item); order_result($items, 'history_sort', $sortorder); foreach ($items as &$item) { unset($item['history_sort']); } unset($item); } /** * Order items by keep trends. * * @param array $items * @param int $items['value_type'] * @param string $items['trends'] * @param string $sortorder */ function orderItemsByTrends(array &$items, $sortorder){ $simple_interval_parser = new CSimpleIntervalParser(); foreach ($items as &$item) { if (in_array($item['value_type'], [ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_TEXT])) { $item['trends_sort'] = ''; } else { $item['trends_sort'] = ($simple_interval_parser->parse($item['trends']) == CParser::PARSE_SUCCESS) ? timeUnitToSeconds($item['trends']) : $item['trends']; } } unset($item); order_result($items, 'trends_sort', $sortorder); foreach ($items as &$item) { unset($item['trends_sort']); } unset($item); } /** * Order items by update interval. * * @param array $items * @param int $items['type'] * @param string $items['delay'] * @param string $items['key_'] * @param string $sortorder * @param array $options * @param bool $options['usermacros'] * @param bool $options['lldmacros'] */ function orderItemsByDelay(array &$items, $sortorder, array $options){ $update_interval_parser = new CUpdateIntervalParser($options); foreach ($items as &$item) { if (in_array($item['type'], [ITEM_TYPE_TRAPPER, ITEM_TYPE_SNMPTRAP, ITEM_TYPE_DEPENDENT]) || ($item['type'] == ITEM_TYPE_ZABBIX_ACTIVE && strncmp($item['key_'], 'mqtt.get', 8) === 0)) { $item['delay_sort'] = ''; } elseif ($update_interval_parser->parse($item['delay']) == CParser::PARSE_SUCCESS) { $item['delay_sort'] = $update_interval_parser->getDelay(); if ($item['delay_sort'][0] !== '{') { $item['delay_sort'] = timeUnitToSeconds($item['delay_sort']); } } else { $item['delay_sort'] = $item['delay']; } } unset($item); order_result($items, 'delay_sort', $sortorder); foreach ($items as &$item) { unset($item['delay_sort']); } unset($item); } /** * Orders items by both status and state. Items are sorted in the following order: enabled, disabled, not supported. * * Keep in sync with orderTriggersByStatus(). * * @param array $items * @param string $sortorder */ function orderItemsByStatus(array &$items, $sortorder = ZBX_SORT_UP) { $sort = []; foreach ($items as $key => $item) { if ($item['status'] == ITEM_STATUS_ACTIVE) { $sort[$key] = ($item['state'] == ITEM_STATE_NOTSUPPORTED) ? 2 : 0; } else { $sort[$key] = 1; } } if ($sortorder == ZBX_SORT_UP) { asort($sort); } else { arsort($sort); } $sortedItems = []; foreach ($sort as $key => $val) { $sortedItems[$key] = $items[$key]; } $items = $sortedItems; } /** * Returns the name of the given interface type. Items "status" and "state" properties must be defined. * * @param int $type * * @return ?string */ function interfaceType2str($type) { $interfaceGroupLabels = [ INTERFACE_TYPE_AGENT => _('Agent'), INTERFACE_TYPE_SNMP => _('SNMP'), INTERFACE_TYPE_JMX => _('JMX'), INTERFACE_TYPE_IPMI => _('IPMI') ]; return array_key_exists($type, $interfaceGroupLabels) ? $interfaceGroupLabels[$type] : null; } function itemTypeInterface($type = null) { static $types = [ ITEM_TYPE_SNMP => INTERFACE_TYPE_SNMP, ITEM_TYPE_SNMPTRAP => INTERFACE_TYPE_SNMP, ITEM_TYPE_IPMI => INTERFACE_TYPE_IPMI, ITEM_TYPE_ZABBIX => INTERFACE_TYPE_AGENT, ITEM_TYPE_SIMPLE => INTERFACE_TYPE_OPT, ITEM_TYPE_EXTERNAL => INTERFACE_TYPE_OPT, ITEM_TYPE_SSH => INTERFACE_TYPE_OPT, ITEM_TYPE_TELNET => INTERFACE_TYPE_OPT, ITEM_TYPE_JMX => INTERFACE_TYPE_JMX, ITEM_TYPE_HTTPAGENT => INTERFACE_TYPE_OPT ]; if (is_null($type)) { return $types; } elseif (array_key_exists($type, $types)) { return $types[$type]; } return false; } /** * Convert a list of interfaces to an array of interface type => interfaceids. * * @param array $interfaces List of (host) interfaces. * * @return array Interface IDs grouped by type. */ function interfaceIdsByType(array $interfaces) { $interface_ids_by_type = []; foreach ($interfaces as $interface) { $interface_ids_by_type[$interface['type']][] = $interface['interfaceid']; } return $interface_ids_by_type; } /** * Get parent templates for each given item. * * @param array $items An array of items. * @param string $items[]['itemid'] ID of an item. * @param string $items[]['templateid'] ID of parent template item. * @param int $flag Origin of the item (ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_RULE, * ZBX_FLAG_DISCOVERY_PROTOTYPE). * * @return array */ function getItemParentTemplates(array $items, $flag) { $parent_itemids = []; $data = [ 'links' => [], 'templates' => [] ]; foreach ($items as $item) { if ($item['templateid'] != 0) { $parent_itemids[$item['templateid']] = true; $data['links'][$item['itemid']] = ['itemid' => $item['templateid']]; } } if (!$parent_itemids) { return $data; } $all_parent_itemids = []; $hostids = []; if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) { $lld_ruleids = []; } do { if ($flag == ZBX_FLAG_DISCOVERY_RULE) { $db_items = API::DiscoveryRule()->get([ 'output' => ['itemid', 'hostid', 'templateid'], 'itemids' => array_keys($parent_itemids) ]); } elseif ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) { $db_items = API::ItemPrototype()->get([ 'output' => ['itemid', 'hostid', 'templateid'], 'itemids' => array_keys($parent_itemids), 'selectDiscoveryRule' => ['itemid'] ]); } // ZBX_FLAG_DISCOVERY_NORMAL else { $db_items = API::Item()->get([ 'output' => ['itemid', 'hostid', 'templateid'], 'itemids' => array_keys($parent_itemids), 'webitems' => true ]); } $all_parent_itemids += $parent_itemids; $parent_itemids = []; foreach ($db_items as $db_item) { $data['templates'][$db_item['hostid']] = []; $hostids[$db_item['itemid']] = $db_item['hostid']; if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) { $lld_ruleids[$db_item['itemid']] = $db_item['discoveryRule']['itemid']; } if ($db_item['templateid'] != 0) { if (!array_key_exists($db_item['templateid'], $all_parent_itemids)) { $parent_itemids[$db_item['templateid']] = true; } $data['links'][$db_item['itemid']] = ['itemid' => $db_item['templateid']]; } } } while ($parent_itemids); foreach ($data['links'] as &$parent_item) { $parent_item['hostid'] = array_key_exists($parent_item['itemid'], $hostids) ? $hostids[$parent_item['itemid']] : 0; if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) { $parent_item['lld_ruleid'] = array_key_exists($parent_item['itemid'], $lld_ruleids) ? $lld_ruleids[$parent_item['itemid']] : 0; } } unset($parent_item); $db_templates = $data['templates'] ? API::Template()->get([ 'output' => ['name'], 'templateids' => array_keys($data['templates']), 'preservekeys' => true ]) : []; $rw_templates = $db_templates ? API::Template()->get([ 'output' => [], 'templateids' => array_keys($db_templates), 'editable' => true, 'preservekeys' => true ]) : []; $data['templates'][0] = []; foreach ($data['templates'] as $hostid => &$template) { $template = array_key_exists($hostid, $db_templates) ? [ 'hostid' => $hostid, 'name' => $db_templates[$hostid]['name'], 'permission' => array_key_exists($hostid, $rw_templates) ? PERM_READ_WRITE : PERM_READ ] : [ 'hostid' => $hostid, 'name' => _('Inaccessible template'), 'permission' => PERM_DENY ]; } unset($template); return $data; } /** * Returns a template prefix for selected item. * * @param string $itemid * @param array $parent_templates The list of the templates, prepared by getItemParentTemplates() function. * @param int $flag Origin of the item (ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_RULE, * ZBX_FLAG_DISCOVERY_PROTOTYPE). * @param bool $provide_links If this parameter is false, prefix will not contain links. * * @return array|null */ function makeItemTemplatePrefix($itemid, array $parent_templates, $flag, bool $provide_links) { if (!array_key_exists($itemid, $parent_templates['links'])) { return null; } while (array_key_exists($parent_templates['links'][$itemid]['itemid'], $parent_templates['links'])) { $itemid = $parent_templates['links'][$itemid]['itemid']; } $template = $parent_templates['templates'][$parent_templates['links'][$itemid]['hostid']]; if ($provide_links && $template['permission'] == PERM_READ_WRITE) { if ($flag == ZBX_FLAG_DISCOVERY_RULE) { $url = (new CUrl('host_discovery.php')) ->setArgument('filter_set', '1') ->setArgument('filter_hostids', [$template['hostid']]) ->setArgument('context', 'template'); } elseif ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) { $url = (new CUrl('disc_prototypes.php')) ->setArgument('parent_discoveryid', $parent_templates['links'][$itemid]['lld_ruleid']) ->setArgument('context', 'template'); } // ZBX_FLAG_DISCOVERY_NORMAL else { $url = (new CUrl('items.php')) ->setArgument('filter_set', '1') ->setArgument('filter_hostids', [$template['hostid']]) ->setArgument('context', 'template'); } $name = (new CLink($template['name'], $url))->addClass(ZBX_STYLE_LINK_ALT); } else { $name = new CSpan($template['name']); } return [$name->addClass(ZBX_STYLE_GREY), NAME_DELIMITER]; } /** * Returns a list of item templates. * * @param string $itemid * @param array $parent_templates The list of the templates, prepared by getItemParentTemplates() function. * @param int $flag Origin of the item (ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_RULE, * ZBX_FLAG_DISCOVERY_PROTOTYPE). * @param bool $provide_links If this parameter is false, prefix will not contain links. * * @return array */ function makeItemTemplatesHtml($itemid, array $parent_templates, $flag, bool $provide_links) { $list = []; while (array_key_exists($itemid, $parent_templates['links'])) { $template = $parent_templates['templates'][$parent_templates['links'][$itemid]['hostid']]; if ($provide_links && $template['permission'] == PERM_READ_WRITE) { if ($flag == ZBX_FLAG_DISCOVERY_RULE) { $url = (new CUrl('host_discovery.php')) ->setArgument('form', 'update') ->setArgument('itemid', $parent_templates['links'][$itemid]['itemid']) ->setArgument('context', 'template'); } elseif ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) { $url = (new CUrl('disc_prototypes.php')) ->setArgument('form', 'update') ->setArgument('itemid', $parent_templates['links'][$itemid]['itemid']) ->setArgument('parent_discoveryid', $parent_templates['links'][$itemid]['lld_ruleid']) ->setArgument('context', 'template'); } // ZBX_FLAG_DISCOVERY_NORMAL else { $url = (new CUrl('items.php')) ->setArgument('form', 'update') ->setArgument('itemid', $parent_templates['links'][$itemid]['itemid']) ->setArgument('context', 'template'); } $name = new CLink($template['name'], $url); } else { $name = (new CSpan($template['name']))->addClass(ZBX_STYLE_GREY); } array_unshift($list, $name, [NBSP(), RARR(), NBSP()]); $itemid = $parent_templates['links'][$itemid]['itemid']; } if ($list) { array_pop($list); } return $list; } /** * Collect latest value and actual severity value for each item of Data overview table. * * @param array $db_items * @param array $data * @param int $show_suppressed * * @return array */ function getDataOverviewCellData(array $db_items, array $data, int $show_suppressed): array { $history = Manager::History()->getLastValues($db_items, 1, timeUnitToSeconds(CSettingsHelper::get(CSettingsHelper::HISTORY_PERIOD)) ); $db_triggers = getTriggersWithActualSeverity([ 'output' => ['triggerid', 'priority', 'value'], 'selectItems' => ['itemid'], 'itemids' => array_keys($db_items), 'monitored' => true, 'preservekeys' => true ], ['show_suppressed' => $show_suppressed]); $itemid_to_triggerids = []; foreach ($db_triggers as $triggerid => $db_trigger) { foreach ($db_trigger['items'] as $item) { if (!array_key_exists($item['itemid'], $itemid_to_triggerids)) { $itemid_to_triggerids[$item['itemid']] = []; } $itemid_to_triggerids[$item['itemid']][] = $triggerid; } } // Apply values and trigger severity to each $data cell. foreach ($data as &$data_clusters) { foreach ($data_clusters as &$data_cluster) { foreach ($data_cluster as &$item) { $itemid = $item['itemid']; if (array_key_exists($itemid, $itemid_to_triggerids)) { $max_priority = -1; $max_priority_triggerid = -1; foreach ($itemid_to_triggerids[$itemid] as $triggerid) { $trigger = $db_triggers[$triggerid]; // Bump lower priority triggers of value "true" ahead of triggers with value "false". $multiplier = ($trigger['value'] == TRIGGER_VALUE_TRUE) ? TRIGGER_SEVERITY_COUNT : 0; if ($trigger['priority'] + $multiplier > $max_priority) { $max_priority_triggerid = $triggerid; $max_priority = $trigger['priority'] + $multiplier; } } $trigger = $db_triggers[$max_priority_triggerid]; } else { $trigger = null; } $item += [ 'value' => array_key_exists($itemid, $history) ? $history[$itemid][0]['value'] : null, 'trigger' => $trigger ]; } } } unset($data_clusters, $data_cluster, $item); return $data; } /** * @param array $groupids * @param array $hostids * @param array $tags * @param int $evaltype * * @return array */ function getDataOverviewItems(?array $groupids, ?array $hostids, ?array $tags, int $evaltype): array { if ($hostids === null) { $limit = CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT); $db_hosts = API::Host()->get([ 'output' => ['name'], 'groupids' => $groupids, 'monitored_hosts' => true, 'with_monitored_items' => true, 'sortfield' => ['name'], 'limit' => $limit, 'preservekeys' => true ]); CArrayHelper::sort($db_hosts, ['name']); $db_hosts = array_slice($db_hosts, 0, CSettingsHelper::get(CSettingsHelper::MAX_OVERVIEW_TABLE_SIZE) + 1, true); $hostids = array_keys($db_hosts); } $db_items = API::Item()->get([ 'output' => ['itemid', 'hostid', 'name', 'value_type', 'units', 'valuemapid'], 'selectHosts' => ['name'], 'selectValueMap' => ['mappings'], 'hostids' => $hostids, 'groupids' => $groupids, 'evaltype' => $evaltype, 'tags' => $tags, 'monitored' => true, 'webitems' => true, 'preservekeys' => true ]); CArrayHelper::sort($db_items, [ ['field' => 'name', 'order' => ZBX_SORT_UP], ['field' => 'itemid', 'order' => ZBX_SORT_UP] ]); return $db_items; } /** * @param array $groupids * @param array $hostids * @param array $filter * @param array $filter['tags'] * @param int $filter['evaltype'] * @param int $filter['show_suppressed'] * * @return array */ function getDataOverview(?array $groupids, ?array $hostids, array $filter): array { $tags = (array_key_exists('tags', $filter) && $filter['tags']) ? $filter['tags'] : null; $evaltype = array_key_exists('evaltype', $filter) ? $filter['evaltype'] : TAG_EVAL_TYPE_AND_OR; $db_items = getDataOverviewItems($groupids, $hostids, $tags, $evaltype); $data = []; $item_counter = []; $db_hosts = []; foreach ($db_items as $db_item) { $item_name = $db_item['name']; $host_name = $db_item['hosts'][0]['name']; $db_hosts[$db_item['hostid']] = $db_item['hosts'][0]; if (!array_key_exists($host_name, $item_counter)) { $item_counter[$host_name] = []; } if (!array_key_exists($item_name, $item_counter[$host_name])) { $item_counter[$host_name][$item_name] = 0; } $item_place = $item_counter[$host_name][$item_name]; $item_counter[$host_name][$item_name]++; $item = [ 'itemid' => $db_item['itemid'], 'value_type' => $db_item['value_type'], 'units' => $db_item['units'], 'valuemap' => $db_item['valuemap'], 'acknowledged' => array_key_exists('acknowledged', $db_item) ? $db_item['acknowledged'] : 0 ]; if (array_key_exists('triggerid', $db_item)) { $item += [ 'triggerid' => $db_item['triggerid'], 'severity' => $db_item['priority'], 'tr_value' => $db_item['value'] ]; } else { $item += [ 'triggerid' => null, 'severity' => null, 'tr_value' => null ]; } $data[$item_name][$item_place][$host_name] = $item; } CArrayHelper::sort($db_hosts, [ ['field' => 'name', 'order' => ZBX_SORT_UP] ]); $data_display_limit = (int) CSettingsHelper::get(CSettingsHelper::MAX_OVERVIEW_TABLE_SIZE); $has_hidden_data = count($data) > $data_display_limit || count($db_hosts) > $data_display_limit; $db_hosts = array_slice($db_hosts, 0, $data_display_limit, true); $host_names = array_column($db_hosts, 'name', 'name'); $itemids = []; $items_left = $data_display_limit; foreach ($data as &$item_columns) { if ($items_left != 0) { $item_columns = array_slice($item_columns, 0, min($data_display_limit, $items_left)); $items_left -= count($item_columns); } else { $item_columns = null; break; } foreach ($item_columns as &$item_column) { CArrayHelper::ksort($item_column); $item_column = array_slice($item_column, 0, $data_display_limit, true); foreach ($item_column as $host_name => $item) { if (array_key_exists($host_name, $host_names)) { $itemids[$item['itemid']] = true; } else { unset($item_column[$host_name]); } } } unset($item_column); $item_columns = array_filter($item_columns); } unset($item_columns); $data = array_filter($data); $data = array_slice($data, 0, $data_display_limit, true); $has_hidden_data = $has_hidden_data || count($db_items) != count($itemids); $db_items = array_intersect_key($db_items, $itemids); $data = getDataOverviewCellData($db_items, $data, $filter['show_suppressed']); return [$data, $db_hosts, $has_hidden_data]; } /** * Prepares interfaces select element with options. * * @param array $interfaces * * @return CSelect */ function getInterfaceSelect(array $interfaces): CSelect { $interface_select = new CSelect('interfaceid'); /** @var CSelectOption[] $options_by_type */ $options_by_type = []; $interface_select->addOption(new CSelectOption(0, _('None'))); foreach ($interfaces as $interface) { $option = new CSelectOption($interface['interfaceid'], getHostInterface($interface)); if ($interface['type'] == INTERFACE_TYPE_SNMP) { $option->setExtra('description', getSnmpInterfaceDescription($interface)); } $options_by_type[$interface['type']][] = $option; } foreach (CItem::INTERFACE_TYPES_BY_PRIORITY as $interface_type) { if (array_key_exists($interface_type, $options_by_type)) { $interface_group = new CSelectOptionGroup((string) interfaceType2str($interface_type)); if ($interface_type == INTERFACE_TYPE_SNMP) { $interface_group->setOptionTemplate('#{label}'.(new CDiv('#{description}'))->addClass('description')); } $interface_group->addOptions($options_by_type[$interface_type]); $interface_select->addOptionGroup($interface_group); } } return $interface_select; } /** * Creates SNMP interface description. * * @param array $interface * @param int $interface['details']['version'] Interface SNMP version. * @param int $interface['details']['contextname'] Interface context name for SNMP version 3. * @param int $interface['details']['community'] Interface community for SNMP non version 3 interface. * @param int $interface['details']['securitylevel'] Security level for SNMP version 3 interface. * @param int $interface['details']['authprotocol'] Authentication protocol for SNMP version 3 interface. * @param int $interface['details']['privprotocol'] Privacy protocol for SNMP version 3 interface. * * @return string */ function getSnmpInterfaceDescription(array $interface): string { $snmp_desc = [ _s('SNMPv%1$d', $interface['details']['version']) ]; if ($interface['details']['version'] == SNMP_V3) { $snmp_desc[] = _('Context name').': '.$interface['details']['contextname']; if ($interface['details']['securitylevel'] == ITEM_SNMPV3_SECURITYLEVEL_AUTHPRIV) { [$interface['details']['authprotocol'] => $auth_protocol] = getSnmpV3AuthProtocols(); [$interface['details']['privprotocol'] => $priv_protocol] = getSnmpV3PrivProtocols(); $snmp_desc[] = '(priv: '.$priv_protocol.', auth: '.$auth_protocol.')'; } elseif ($interface['details']['securitylevel'] == ITEM_SNMPV3_SECURITYLEVEL_AUTHNOPRIV) { [$interface['details']['authprotocol'] => $auth_protocol] = getSnmpV3AuthProtocols(); $snmp_desc[] = '(auth: '.$auth_protocol.')'; } } else { $snmp_desc[] = _x('Community', 'SNMP Community').': '.$interface['details']['community']; } return implode(', ', $snmp_desc); } /** * Named SNMPv3 authentication protocols. * * @return array */ function getSnmpV3AuthProtocols(): array { return [ ITEM_SNMPV3_AUTHPROTOCOL_MD5 => 'MD5', ITEM_SNMPV3_AUTHPROTOCOL_SHA1 => 'SHA1', ITEM_SNMPV3_AUTHPROTOCOL_SHA224 => 'SHA224', ITEM_SNMPV3_AUTHPROTOCOL_SHA256 => 'SHA256', ITEM_SNMPV3_AUTHPROTOCOL_SHA384 => 'SHA384', ITEM_SNMPV3_AUTHPROTOCOL_SHA512 => 'SHA512' ]; } /** * Named SNMPv3 privacy protocols. * * @return array */ function getSnmpV3PrivProtocols(): array { return [ ITEM_SNMPV3_PRIVPROTOCOL_DES => 'DES', ITEM_SNMPV3_PRIVPROTOCOL_AES128 => 'AES128', ITEM_SNMPV3_PRIVPROTOCOL_AES192 => 'AES192', ITEM_SNMPV3_PRIVPROTOCOL_AES256 => 'AES256', ITEM_SNMPV3_PRIVPROTOCOL_AES192C => 'AES192C', ITEM_SNMPV3_PRIVPROTOCOL_AES256C => 'AES256C' ]; } /** * @param array $item * @param array $trigger * * @return CCol */ function getItemDataOverviewCell(array $item, ?array $trigger = null): CCol { $ack = null; $css = ''; $value = UNKNOWN_VALUE; if ($trigger && $trigger['value'] == TRIGGER_VALUE_TRUE) { $css = CSeverityHelper::getStyle((int) $trigger['priority']); if ($trigger['problem']['acknowledged'] == 1) { $ack = [' ', (new CSpan())->addClass(ZBX_ICON_CHECK)]; } } if ($item['value'] !== null) { $value = $item['value_type'] == ITEM_VALUE_TYPE_BINARY ? italic(_('binary value'))->addClass(ZBX_STYLE_GREY) : formatHistoryValue($item['value'], $item); } $col = (new CCol([$value, $ack])) ->addClass($css) ->addClass(ZBX_STYLE_NOWRAP) ->setMenuPopup(CMenuPopupHelper::getHistory($item['itemid'])) ->addClass(ZBX_STYLE_CURSOR_POINTER); return $col; } /** * Prepare item value for displaying, apply value map and/or convert units. * * @see formatHistoryValueRaw * * @param int|float|string $value * @param array $item * @param bool $trim Whether to trim non-numeric value to a length of 20 characters. * @param array $convert_options Options for unit conversion. See @convertUnitsRaw. * * @return string */ function formatHistoryValue($value, array $item, bool $trim = true, array $convert_options = []): string { $formatted_value = formatHistoryValueRaw($value, $item, $trim, $convert_options); return $formatted_value['value'].($formatted_value['units'] !== '' ? ' '.$formatted_value['units'] : ''); } /** * Prepare item value for displaying, apply value map and/or convert units. * * @param int|float|string $value * @param array $item * @param bool $trim Whether to trim non-numeric value to a length of 20 characters. * @param array $convert_options Options for unit conversion. See @convertUnitsRaw. * * $item = [ * 'value_type' => (int) ITEM_VALUE_TYPE_FLOAT | ITEM_VALUE_TYPE_UINT64, ... * 'units' => (string) Item units. * 'valuemap' => (array) Item value map. * ] * * @return array */ function formatHistoryValueRaw($value, array $item, bool $trim = true, array $convert_options = []): array { $mapped_value = in_array($item['value_type'], [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64, ITEM_VALUE_TYPE_STR]) ? CValueMapHelper::getMappedValue($item['value_type'], $value, $item['valuemap']) : false; switch ($item['value_type']) { case ITEM_VALUE_TYPE_FLOAT: case ITEM_VALUE_TYPE_UINT64: if ($mapped_value !== false) { return [ 'value' => $mapped_value.' ('.$value.')', 'units' => '', 'is_mapped' => true ]; } $converted_value = convertUnitsRaw([ 'value' => $value, 'units' => $item['units'] ] + $convert_options); return [ 'value' => $converted_value['value'], 'units' => $converted_value['units'], 'is_mapped' => false ]; case ITEM_VALUE_TYPE_STR: case ITEM_VALUE_TYPE_TEXT: case ITEM_VALUE_TYPE_LOG: if ($trim && mb_strlen($value) > 20) { $value = mb_substr($value, 0, 20).'...'; } if ($mapped_value !== false) { $value = $mapped_value.' ('.$value.')'; } return [ 'value' => $value, 'units' => '', 'is_mapped' => $mapped_value !== false ]; case ITEM_VALUE_TYPE_BINARY: return [ 'value' => _('binary value'), 'units' => '', 'is_mapped' => false ]; default: return [ 'value' => _('Unknown value type'), 'units' => '', 'is_mapped' => false ]; } } /** * Check whether the unit of an item is binary or not. * * @param string $units * * @return bool */ function isBinaryUnits(string $units): bool { return $units === 'B' || $units === 'Bps'; } /** * Retrieves from DB historical data for items and applies functional calculations. * If fails for some reason, returns null. * * @param array $item * @param string $item['itemid'] ID of item * @param string $item['value_type'] type of item, allowed: ITEM_VALUE_TYPE_FLOAT and ITEM_VALUE_TYPE_UINT64 * @param string $function function to apply to time period from param, allowed: min, max and avg * @param string $parameter formatted parameter for function, example: "2w" meaning 2 weeks * * @return string|null item functional value from history */ function getItemFunctionalValue($item, $function, $parameter) { // Check whether function is allowed and parameter is specified. if (!in_array($function, ['min', 'max', 'avg']) || $parameter === '') { return null; } // Check whether item type is allowed for min, max and avg functions. if ($item['value_type'] != ITEM_VALUE_TYPE_FLOAT && $item['value_type'] != ITEM_VALUE_TYPE_UINT64) { return null; } $number_parser = new CNumberParser(['with_size_suffix' => true, 'with_time_suffix' => true]); if ($number_parser->parse($parameter) != CParser::PARSE_SUCCESS) { return null; } $parameter = $number_parser->calcValue(); $time_from = time() - $parameter; if ($time_from < 0 || $time_from > ZBX_MAX_DATE) { return null; } return Manager::History()->getAggregatedValue($item, $function, $time_from); } /** * Check if current time is within the given period. * * @param string $period time period format: "wd[-wd2],hh:mm-hh:mm" * @param int $now current timestamp * * @return bool true - within period, false - out of period */ function checkTimePeriod($period, $now) { if (sscanf($period, '%d-%d,%d:%d-%d:%d', $d1, $d2, $h1, $m1, $h2, $m2) != 6) { if (sscanf($period, '%d,%d:%d-%d:%d', $d1, $h1, $m1, $h2, $m2) != 5) { // delay period format is wrong - skip return false; } $d2 = $d1; } $tm = localtime($now, true); $day = ($tm['tm_wday'] == 0) ? 7 : $tm['tm_wday']; $sec = SEC_PER_HOUR * $tm['tm_hour'] + SEC_PER_MIN * $tm['tm_min'] + $tm['tm_sec']; $sec1 = SEC_PER_HOUR * $h1 + SEC_PER_MIN * $m1; $sec2 = SEC_PER_HOUR * $h2 + SEC_PER_MIN * $m2; return $d1 <= $day && $day <= $d2 && $sec1 <= $sec && $sec < $sec2; } /** * Get item minimum delay. * * @param string $delay * @param array $flexible_intervals * * @return string */ function getItemDelay($delay, array $flexible_intervals) { $delay = timeUnitToSeconds($delay); if ($delay != 0 || !$flexible_intervals) { return $delay; } $min_delay = SEC_PER_YEAR; foreach ($flexible_intervals as $flexible_interval) { $flexible_interval_parts = explode('/', $flexible_interval); $flexible_delay = timeUnitToSeconds($flexible_interval_parts[0]); $min_delay = min($min_delay, $flexible_delay); } return $min_delay; } /** * Return delay value that is currently applicable * * @param int $delay default delay * @param array $flexible_intervals array of intervals in format: "d/wd[-wd2],hh:mm-hh:mm" * @param int $now current timestamp * * @return int delay for a current timestamp */ function getCurrentDelay($delay, array $flexible_intervals, $now) { if (!$flexible_intervals) { return $delay; } $current_delay = -1; foreach ($flexible_intervals as $flexible_interval) { list($flexible_delay, $flexible_period) = explode('/', $flexible_interval); $flexible_delay = (int) $flexible_delay; if (($current_delay == -1 || $flexible_delay < $current_delay) && checkTimePeriod($flexible_period, $now)) { $current_delay = $flexible_delay; } } if ($current_delay == -1) { return $delay; } return $current_delay; } /** * Return time of next flexible interval * * @param array $flexible_intervals array of intervals in format: "d/wd[-wd2],hh:mm-hh:mm" * @param int $now current timestamp * @param int $next_interval timestamp of a next interval * * @return bool false if no flexible intervals defined */ function getNextDelayInterval(array $flexible_intervals, $now, &$next_interval) { if (!$flexible_intervals) { return false; } $next = 0; $tm = localtime($now, true); $day = ($tm['tm_wday'] == 0) ? 7 : $tm['tm_wday']; $sec = SEC_PER_HOUR * $tm['tm_hour'] + SEC_PER_MIN * $tm['tm_min'] + $tm['tm_sec']; foreach ($flexible_intervals as $flexible_interval) { $flexible_interval_parts = explode('/', $flexible_interval); if (sscanf($flexible_interval_parts[1], '%d-%d,%d:%d-%d:%d', $d1, $d2, $h1, $m1, $h2, $m2) != 6) { if (sscanf($flexible_interval_parts[1], '%d,%d:%d-%d:%d', $d1, $h1, $m1, $h2, $m2) != 5) { continue; } $d2 = $d1; } $sec1 = SEC_PER_HOUR * $h1 + SEC_PER_MIN * $m1; $sec2 = SEC_PER_HOUR * $h2 + SEC_PER_MIN * $m2; // current period if ($d1 <= $day && $day <= $d2 && $sec1 <= $sec && $sec < $sec2) { if ($next == 0 || $next > $now - $sec + $sec2) { // the next second after the current interval's upper bound $next = $now - $sec + $sec2; } } // will be active today elseif ($d1 <= $day && $d2 >= $day && $sec < $sec1) { if ($next == 0 || $next > $now - $sec + $sec1) { $next = $now - $sec + $sec1; } } else { $nextDay = ($day + 1 <= 7) ? $day + 1 : 1; // will be active tomorrow if ($d1 <= $nextDay && $nextDay <= $d2) { if ($next == 0 || $next > $now - $sec + SEC_PER_DAY + $sec1) { $next = $now - $sec + SEC_PER_DAY + $sec1; } } // later in the future else { $dayDiff = -1; if ($day < $d1) { $dayDiff = $d1 - $day; } if ($day >= $d2) { $dayDiff = ($d1 + 7) - $day; } if ($d1 <= $day && $day < $d2) { // should never happen, could not deduce day difference $dayDiff = -1; } if ($dayDiff != -1 && ($next == 0 || $next > $now - $sec + SEC_PER_DAY * $dayDiff + $sec1)) { $next = $now - $sec + SEC_PER_DAY * $dayDiff + $sec1; } } } } if ($next != 0) { $next_interval = $next; } return ($next != 0); } /** * Calculate nextcheck timestamp for an item using flexible intervals. * * the parameter $flexible_intervals is an array if strings that are in the following format: * * +------------[;]<----------+ * | | * ->+-[d/wd[-wd2],hh:mm-hh:mm]-+ * * d - delay (0-n) * wd, wd2 - day of week (1-7) * hh - hours (0-24) * mm - minutes (0-59) * * @param int $seed seed value applied to delay to spread item checks over the delay period * @param string $delay default delay, can be overridden * @param array $flexible_intervals array of flexible intervals * @param int $now current timestamp * * @return int */ function calculateItemNextCheck($seed, $delay, $flexible_intervals, $now) { /* * Try to find the nearest 'nextcheck' value with condition 'now' < 'nextcheck' < 'now' + SEC_PER_YEAR * If it is not possible to check the item within a year, fail. */ $t = $now; $tMax = $now + SEC_PER_YEAR; $try = 0; while ($t < $tMax) { // Calculate 'nextcheck' value for the current interval. $currentDelay = getCurrentDelay($delay, $flexible_intervals, $t); if ($currentDelay != 0) { $nextCheck = $currentDelay * floor($t / $currentDelay) + ($seed % $currentDelay); if ($try == 0) { while ($nextCheck <= $t) { $nextCheck += $currentDelay; } } else { while ($nextCheck < $t) { $nextCheck += $currentDelay; } } } else { $nextCheck = ZBX_JAN_2038; } /* * Is 'nextcheck' < end of the current interval and the end of the current interval * is the beginning of the next interval - 1. */ if (getNextDelayInterval($flexible_intervals, $t, $nextInterval) && $nextCheck >= $nextInterval) { // 'nextcheck' is beyond the current interval. $t = $nextInterval; $try++; } else { break; } } return $nextCheck; } /* * Description: * Function returns true if http items exists in the $items array. * The array should contain a field 'type' */ function httpItemExists($items) { foreach ($items as $item) { if ($item['type'] == ITEM_TYPE_HTTPTEST) { return true; } } return false; } function getParamFieldNameByType($itemType) { switch ($itemType) { case ITEM_TYPE_SCRIPT: return 'script'; case ITEM_TYPE_SSH: case ITEM_TYPE_TELNET: case ITEM_TYPE_JMX: return 'params_es'; case ITEM_TYPE_DB_MONITOR: return 'params_ap'; case ITEM_TYPE_CALCULATED: return 'params_f'; default: return 'params'; } } function getParamFieldLabelByType($itemType) { switch ($itemType) { case ITEM_TYPE_SCRIPT: return _('Script'); case ITEM_TYPE_SSH: case ITEM_TYPE_TELNET: case ITEM_TYPE_JMX: return _('Executed script'); case ITEM_TYPE_DB_MONITOR: return _('SQL query'); case ITEM_TYPE_CALCULATED: return _('Formula'); default: return 'params'; } } /** * Get either one or all item preprocessing types. * If $grouped set to true, returns group labels. Returns empty string if no specific type is found. * * Usage examples: * - get_preprocessing_types(null, true, [5, 4, 2]) Returns array as defined. * - get_preprocessing_types(4, true, [5, 4, 2]) Returns string: 'Trim'. * - get_preprocessing_types(, true, [5, 4, 2]) Returns an empty string: ''. * - get_preprocessing_types(null, false, [5, 12, 15, 16, 20]) Returns subarrays in one array maintaining index: * [5] => Regular expression * [12] => JSONPath * [15] => Does not match regular expression * [16] => Check for error in JSON * [20] => Discard unchanged with heartbeat * * @param int $type Item preprocessing type. * @param bool $grouped Group label flag. If specific type is given, this parameter does not matter. * @param array $supported_types Array of supported pre-processing types. If none are given, empty array is returned. * * @return array|string */ function get_preprocessing_types($type = null, $grouped = true, array $supported_types = []) { $types = [ ZBX_PREPROC_REGSUB => [ 'group' => _('Text'), 'name' => _('Regular expression') ], ZBX_PREPROC_STR_REPLACE => [ 'group' => _('Text'), 'name' => _('Replace') ], ZBX_PREPROC_TRIM => [ 'group' => _('Text'), 'name' => _('Trim') ], ZBX_PREPROC_RTRIM => [ 'group' => _('Text'), 'name' => _('Right trim') ], ZBX_PREPROC_LTRIM => [ 'group' => _('Text'), 'name' => _('Left trim') ], ZBX_PREPROC_XPATH => [ 'group' => _('Structured data'), 'name' => _('XML XPath') ], ZBX_PREPROC_JSONPATH => [ 'group' => _('Structured data'), 'name' => _('JSONPath') ], ZBX_PREPROC_CSV_TO_JSON => [ 'group' => _('Structured data'), 'name' => _('CSV to JSON') ], ZBX_PREPROC_XML_TO_JSON => [ 'group' => _('Structured data'), 'name' => _('XML to JSON') ], ZBX_PREPROC_SNMP_WALK_VALUE => [ 'group' => _('SNMP'), 'name' => _('SNMP walk value') ], ZBX_PREPROC_SNMP_WALK_TO_JSON => [ 'group' => _('SNMP'), 'name' => _('SNMP walk to JSON') ], ZBX_PREPROC_MULTIPLIER => [ 'group' => _('Arithmetic'), 'name' => _('Custom multiplier') ], ZBX_PREPROC_DELTA_VALUE => [ 'group' => _x('Change', 'noun'), 'name' => _('Simple change') ], ZBX_PREPROC_DELTA_SPEED => [ 'group' => _x('Change', 'noun'), 'name' => _('Change per second') ], ZBX_PREPROC_BOOL2DEC => [ 'group' => _('Numeral systems'), 'name' => _('Boolean to decimal') ], ZBX_PREPROC_OCT2DEC => [ 'group' => _('Numeral systems'), 'name' => _('Octal to decimal') ], ZBX_PREPROC_HEX2DEC => [ 'group' => _('Numeral systems'), 'name' => _('Hexadecimal to decimal') ], ZBX_PREPROC_SCRIPT => [ 'group' => _('Custom scripts'), 'name' => _('JavaScript') ], ZBX_PREPROC_VALIDATE_RANGE => [ 'group' => _('Validation'), 'name' => _('In range') ], ZBX_PREPROC_VALIDATE_REGEX => [ 'group' => _('Validation'), 'name' => _('Matches regular expression') ], ZBX_PREPROC_VALIDATE_NOT_REGEX => [ 'group' => _('Validation'), 'name' => _('Does not match regular expression') ], ZBX_PREPROC_ERROR_FIELD_JSON => [ 'group' => _('Validation'), 'name' => _('Check for error in JSON') ], ZBX_PREPROC_ERROR_FIELD_XML => [ 'group' => _('Validation'), 'name' => _('Check for error in XML') ], ZBX_PREPROC_ERROR_FIELD_REGEX => [ 'group' => _('Validation'), 'name' => _('Check for error using regular expression') ], ZBX_PREPROC_VALIDATE_NOT_SUPPORTED => [ 'group' => _('Validation'), 'name' => _('Check for not supported value') ], ZBX_PREPROC_THROTTLE_VALUE => [ 'group' => _('Throttling'), 'name' => _('Discard unchanged') ], ZBX_PREPROC_THROTTLE_TIMED_VALUE => [ 'group' => _('Throttling'), 'name' => _('Discard unchanged with heartbeat') ], ZBX_PREPROC_PROMETHEUS_PATTERN => [ 'group' => _('Prometheus'), 'name' => _('Prometheus pattern') ], ZBX_PREPROC_PROMETHEUS_TO_JSON => [ 'group' => _('Prometheus'), 'name' => _('Prometheus to JSON') ] ]; $filtered_types = []; foreach ($types as $_type => $data) { if (in_array($_type, $supported_types)) { $filtered_types[$data['group']][$_type] = $data['name']; } } $groups = []; foreach ($filtered_types as $label => $types) { $groups[] = [ 'label' => $label, 'types' => $types ]; } if ($type !== null) { foreach ($groups as $group) { if (array_key_exists($type, $group['types'])) { return $group['types'][$type]; } } return ''; } elseif ($grouped) { return $groups; } else { $types = []; foreach ($groups as $group) { $types += $group['types']; } return $types; } } /* * Quoting $param if it contain special characters. * * @param string $param * @param bool $forced * * @return string */ function quoteItemKeyParam($param, $forced = false) { if (!$forced) { if (!isset($param[0]) || ($param[0] != '"' && false === strpbrk($param, ',]'))) { return $param; } } return '"'.str_replace('"', '\\"', $param).'"'; } /** * Expands item name and for dependent item master item name. * * @param array $items Array of items. * @param string $data_source 'items' or 'itemprototypes'. * * @return array */ function expandItemNamesWithMasterItems($items, $data_source) { $itemids = []; $master_itemids = []; foreach ($items as $item_index => &$item) { if ($item['type'] == ITEM_TYPE_DEPENDENT) { $master_itemids[$item['master_itemid']] = true; } // The "source" is required to tell the frontend where the link should point at - item or item prototype. $item['source'] = $data_source; $itemids[$item_index] = $item['itemid']; } unset($item); $master_itemids = array_diff(array_keys($master_itemids), $itemids); if ($master_itemids) { $options = [ 'output' => ['itemid', 'type', 'name'], 'itemids' => $master_itemids, 'editable' => true, 'preservekeys' => true ]; $master_items = API::Item()->get($options + ['webitems' => true]); foreach ($master_items as &$master_item) { $master_item['source'] = 'items'; } unset($master_item); $master_item_prototypes = API::ItemPrototype()->get($options); foreach ($master_item_prototypes as &$master_item_prototype) { $master_item_prototype['source'] = 'itemprototypes'; } unset($master_item_prototype); $master_items += $master_item_prototypes; } foreach ($items as &$item) { if ($item['type'] == ITEM_TYPE_DEPENDENT) { $master_itemid = $item['master_itemid']; $items_index = array_search($master_itemid, $itemids); $item['master_item'] = array_fill_keys(['name', 'type', 'source'], ''); $item['master_item'] = ($items_index === false) ? array_intersect_key($master_items[$master_itemid], $item['master_item']) : array_intersect_key($items[$items_index], $item['master_item']); $item['master_item']['itemid'] = $master_itemid; } } unset($item); return $items; } /** * Returns an array of allowed item types for "Check now" functionality. * * @return array */ function checkNowAllowedTypes() { return [ ITEM_TYPE_ZABBIX, ITEM_TYPE_SIMPLE, ITEM_TYPE_INTERNAL, ITEM_TYPE_EXTERNAL, ITEM_TYPE_DB_MONITOR, ITEM_TYPE_IPMI, ITEM_TYPE_SSH, ITEM_TYPE_TELNET, ITEM_TYPE_CALCULATED, ITEM_TYPE_JMX, ITEM_TYPE_DEPENDENT, ITEM_TYPE_HTTPAGENT, ITEM_TYPE_SNMP, ITEM_TYPE_SCRIPT ]; } /** * Validates update interval for items, item prototypes and low-level discovery rules and their overrides. * * @param CUpdateIntervalParser $parser [IN] Parser used for delay validation. * @param string $value [IN] Update interval to parse and validate. * @param string $field_name [IN] Frontend or API field name in the error * @param string $error [OUT] Returned error string if delay validation fails. * * @return bool */ function validateDelay(CUpdateIntervalParser $parser, $field_name, $value, &$error) { if ($parser->parse($value) != CParser::PARSE_SUCCESS) { $error = _s('Incorrect value for field "%1$s": %2$s.', $field_name, _('invalid delay')); return false; } $delay = $parser->getDelay(); if ($delay[0] !== '{') { $delay_sec = timeUnitToSeconds($delay); $intervals = $parser->getIntervals(); $flexible_intervals = $parser->getIntervals(ITEM_DELAY_FLEXIBLE); $has_scheduling_intervals = (bool) $parser->getIntervals(ITEM_DELAY_SCHEDULING); $has_macros = false; foreach ($intervals as $interval) { if (strpos($interval['interval'], '{') !== false) { $has_macros = true; break; } } // If delay is 0, there must be at least one either flexible or scheduling interval. if ($delay_sec == 0 && !$intervals) { $error = _('Item will not be refreshed. Specified update interval requires having at least one either flexible or scheduling interval.'); return false; } elseif ($delay_sec < 0 || $delay_sec > SEC_PER_DAY) { $error = _('Item will not be refreshed. Update interval should be between 1s and 1d. Also Scheduled/Flexible intervals can be used.'); return false; } // If there are scheduling intervals or intervals with macros, skip the next check calculation. if (!$has_macros && !$has_scheduling_intervals && $flexible_intervals && calculateItemNextCheck(0, $delay_sec, $flexible_intervals, time()) == ZBX_JAN_2038) { $error = _('Item will not be refreshed. Please enter a correct update interval.'); return false; } } return true; } /** * Normalizes item preprocessing step parameters after item preprocessing form submit. * * @param array $preprocessing Array of item preprocessing steps, as received from form submit. * * @return array */ function normalizeItemPreprocessingSteps(array $preprocessing): array { foreach ($preprocessing as &$step) { switch ($step['type']) { case ZBX_PREPROC_MULTIPLIER: case ZBX_PREPROC_PROMETHEUS_TO_JSON: $step['params'] = trim($step['params'][0]); break; case ZBX_PREPROC_RTRIM: case ZBX_PREPROC_LTRIM: case ZBX_PREPROC_TRIM: case ZBX_PREPROC_XPATH: case ZBX_PREPROC_JSONPATH: case ZBX_PREPROC_VALIDATE_REGEX: case ZBX_PREPROC_VALIDATE_NOT_REGEX: case ZBX_PREPROC_ERROR_FIELD_JSON: case ZBX_PREPROC_ERROR_FIELD_XML: case ZBX_PREPROC_THROTTLE_TIMED_VALUE: case ZBX_PREPROC_SCRIPT: $step['params'] = $step['params'][0]; break; case ZBX_PREPROC_SNMP_WALK_VALUE: case ZBX_PREPROC_VALIDATE_RANGE: foreach ($step['params'] as &$param) { $param = trim($param); } unset($param); $step['params'] = implode("\n", $step['params']); break; case ZBX_PREPROC_PROMETHEUS_PATTERN: foreach ($step['params'] as &$param) { $param = trim($param); } unset($param); if (in_array($step['params'][1], [ZBX_PREPROC_PROMETHEUS_SUM, ZBX_PREPROC_PROMETHEUS_MIN, ZBX_PREPROC_PROMETHEUS_MAX, ZBX_PREPROC_PROMETHEUS_AVG, ZBX_PREPROC_PROMETHEUS_COUNT])) { $step['params'][2] = $step['params'][1]; $step['params'][1] = ZBX_PREPROC_PROMETHEUS_FUNCTION; } if (!array_key_exists(2, $step['params'])) { $step['params'][2] = ''; } $step['params'] = implode("\n", $step['params']); break; case ZBX_PREPROC_REGSUB: case ZBX_PREPROC_ERROR_FIELD_REGEX: case ZBX_PREPROC_STR_REPLACE: $step['params'] = implode("\n", $step['params']); break; case ZBX_PREPROC_CSV_TO_JSON: if (!array_key_exists(2, $step['params'])) { $step['params'][2] = ZBX_PREPROC_CSV_NO_HEADER; } $step['params'] = implode("\n", $step['params']); break; case ZBX_PREPROC_SNMP_WALK_TO_JSON: $step['params'] = array_values($step['params']); $step['params'] = implode("\n", array_map(function (string $value): string { return trim($value); }, $step['params'])); break; default: $step['params'] = ''; } $step += [ 'error_handler' => ZBX_PREPROC_FAIL_DEFAULT, 'error_handler_params' => '' ]; // Remove fictional fields that don't belong to DB and API. unset($step['sortorder'], $step['on_fail']); } unset($step); return $preprocessing; } /** * Check that the given key is not equal to the example value presented for a specific type. * * @param int $type * @param string $key * * @return bool */ function isItemExampleKey(int $type, string $key): bool { if (($type == ITEM_TYPE_DB_MONITOR && $key === ZBX_DEFAULT_KEY_DB_MONITOR) || ($type == ITEM_TYPE_SSH && $key === ZBX_DEFAULT_KEY_SSH) || ($type == ITEM_TYPE_TELNET && $key === ZBX_DEFAULT_KEY_TELNET)) { error(_('Check the key, please. Default example was passed.')); return true; } return false; } /** * Check the format of the given custom intervals. Unset the custom intervals with empty values. * * @param array $delay_flex * @param bool $lldmacros */ function isValidCustomIntervals(array &$delay_flex, bool $lldmacros = false): bool { if (!$delay_flex) { return true; } $simple_interval_parser = new CSimpleIntervalParser([ 'usermacros' => true, 'lldmacros' => $lldmacros ]); $time_period_parser = new CTimePeriodParser([ 'usermacros' => true, 'lldmacros' => $lldmacros ]); $scheduling_interval_parser = new CSchedulingIntervalParser([ 'usermacros' => true, 'lldmacros' => $lldmacros ]); foreach ($delay_flex as $i => $interval) { if ($interval['type'] == ITEM_DELAY_FLEXIBLE) { if ($interval['delay'] === '' && $interval['period'] === '') { unset($delay_flex[$i]); continue; } if ($simple_interval_parser->parse($interval['delay']) != CParser::PARSE_SUCCESS) { error(_s('Invalid interval "%1$s".', $interval['delay'])); return false; } elseif ($time_period_parser->parse($interval['period']) != CParser::PARSE_SUCCESS) { error(_s('Invalid interval "%1$s".', $interval['period'])); return false; } } else { if ($interval['schedule'] === '') { unset($delay_flex[$i]); continue; } if ($scheduling_interval_parser->parse($interval['schedule']) != CParser::PARSE_SUCCESS) { error(_s('Invalid interval "%1$s".', $interval['schedule'])); return false; } } } return true; } /** * Get all given delay intervals as string in API format. * * @param string $delay * @param array $delay_flex * * @return string */ function getDelayWithCustomIntervals(string $delay, array $delay_flex): string { foreach ($delay_flex as $interval) { if ($interval['type'] == ITEM_DELAY_FLEXIBLE) { $delay .= ';'.$interval['delay'].'/'.$interval['period']; } else { $delay .= ';'.$interval['schedule']; } } return $delay; } /** * Format tags received via form for API input. * * @param array $tags Array of item tags, as received from form submit. * * @return array */ function prepareItemTags(array $tags): array { foreach ($tags as $key => $tag) { if ($tag['tag'] === '' && $tag['value'] === '') { unset($tags[$key]); } elseif (array_key_exists('type', $tag) && !($tag['type'] & ZBX_PROPERTY_OWN)) { unset($tags[$key]); } else { unset($tags[$key]['type']); } } return $tags; } /** * Format LLD macro paths received via form for API input. * * @param array $macro_paths Array of LLD macro paths, as received from form submit. * * @return array */ function prepareLldMacroPaths(array $macro_paths): array { foreach ($macro_paths as $i => &$macro_path) { if ($macro_path['lld_macro'] === '' && $macro_path['path'] === '') { unset($macro_paths[$i]); continue; } $macro_path['lld_macro'] = mb_strtoupper($macro_path['lld_macro']); } unset($macro_path); return array_values($macro_paths); } /** * Format LLD rule filter data received via form for API input. * * @param array $filter Array of LLD filters, as received from form submit. * * @return array */ function prepareLldFilter(array $filter): array { foreach ($filter['conditions'] as $i => &$condition) { if ($condition['macro'] === '' && $condition['value'] === '') { unset($filter['conditions'][$i]); continue; } $condition['macro'] = mb_strtoupper($condition['macro']); if ($filter['evaltype'] != CONDITION_EVAL_TYPE_EXPRESSION) { $condition['formulaid'] = ''; } } unset($condition); $filter['conditions'] = array_values($filter['conditions']); if ($filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION && count($filter['conditions']) <= 1) { $filter['evaltype'] = CONDITION_EVAL_TYPE_AND_OR; $filter['formula'] = ''; if ($filter['conditions']) { $filter['conditions'][0]['formulaid'] = ''; } } if ($filter['evaltype'] != CONDITION_EVAL_TYPE_EXPRESSION) { $filter['formula'] = ''; } return $filter; } /** * Format LLD rule overrides data received via form for API input. * * @param array $overrides Array of LLD overrides, as received from form submit. * @param array|null $db_item * @param array $db_item['overrides'] * * @return array */ function prepareLldOverrides(array $overrides, ?array $db_item): array { $db_overrides = $db_item !== null && $overrides ? array_column($db_item['overrides'], null, 'step') : []; foreach ($overrides as &$override) { if (!array_key_exists($override['step'], $db_overrides) && !array_key_exists('conditions', $override['filter'])) { unset($override['filter']); } elseif (!array_key_exists('conditions', $override['filter'])) { $override['filter']['conditions'] = []; } if (array_key_exists('filter', $override)) { $override['filter'] = prepareLldFilter([ 'evaltype' => $override['filter']['evaltype'], 'formula' => $override['filter']['formula'], 'conditions' => $override['filter']['conditions'] ]); } if (!array_key_exists('operations', $override)) { $override['operations'] = []; } } unset($override); return $overrides; } /** * Format query fields received via form for API input. * * @param array $query_fields * * @return array */ function prepareItemQueryFields(array $query_fields): array { if ($query_fields) { $_query_fields = []; foreach ($query_fields['name'] as $index => $key) { $value = $query_fields['value'][$index]; $sortorder = $query_fields['sortorder'][$index]; if ($key !== '' || $value !== '') { $_query_fields[$sortorder] = [$key => $value]; } } ksort($_query_fields); $query_fields = array_values($_query_fields); } return $query_fields; } /** * Format headers field received via form for API input. * * @param array $headers * * @return array */ function prepareItemHeaders(array $headers): array { if ($headers) { $_headers = []; foreach ($headers['name'] as $i => $name) { $value = $headers['value'][$i]; if ($name === '' && $value === '') { continue; } $_headers[$name] = $value; } $headers = $_headers; } return $headers; } /** * Format parameters field received via form for API input. * * @param array $parameters * * @return array */ function prepareItemParameters(array $parameters): array { $_parameters = []; if (is_array($parameters) && array_key_exists('name', $parameters) && array_key_exists('value', $parameters)) { foreach ($parameters['name'] as $index => $name) { if (array_key_exists($index, $parameters['value']) && ($name !== '' || $parameters['value'][$index] !== '')) { $_parameters[] = [ 'name' => $name, 'value' => $parameters['value'][$index] ]; } } } return $_parameters; } /** * Get sanitized item fields of given input. * * @param array $input * @param string $input['templateid'] * @param int $input['flags'] * @param int $input['type'] * @param string $input['key_'] * @param int $input['value_type'] * @param int $input['authtype'] * @param int $input['allow_traps'] * @param int $input['hosts'][0]['status'] * * @return array */ function getSanitizedItemFields(array $input): array { $field_names = getMainItemFieldNames($input); if ($input['flags'] != ZBX_FLAG_DISCOVERY_CREATED) { $field_names = array_merge($field_names, getTypeItemFieldNames($input)); $field_names = getConditionalItemFieldNames($field_names, $input); } return array_intersect_key($input, array_flip($field_names)); } /** * Get main item fields of given input. * * @param array $input * @param string $input['templateid'] * @param int $input['flags'] * * @return array */ function getMainItemFieldNames(array $input): array { switch ($input['flags']) { case ZBX_FLAG_DISCOVERY_NORMAL: if ($input['templateid'] == 0) { return ['name', 'type', 'key_', 'value_type', 'units', 'history', 'trends', 'valuemapid', 'inventory_link', 'logtimefmt', 'description', 'status', 'tags', 'preprocessing' ]; } else { return ['history', 'trends', 'inventory_link', 'description', 'status', 'tags']; } case ZBX_FLAG_DISCOVERY_RULE: if ($input['templateid'] == 0) { $field_names = ['name', 'type', 'key_', 'lifetime', 'description', 'status', 'preprocessing', 'lld_macro_paths', 'overrides' ]; } else { $field_names = ['lifetime', 'description', 'status']; } if (array_key_exists('itemid', $input) || $input['filter']['conditions']) { $field_names[] = 'filter'; } return $field_names; case ZBX_FLAG_DISCOVERY_PROTOTYPE: if ($input['templateid'] == 0) { return ['name', 'type', 'key_', 'value_type', 'units', 'history', 'trends', 'valuemapid', 'logtimefmt', 'description', 'status', 'discover', 'tags', 'preprocessing' ]; } else { return ['history', 'trends', 'description', 'status', 'discover', 'tags']; } case ZBX_FLAG_DISCOVERY_CREATED: return ['status']; } } /** * Get item field names of the given type and template ID. * * @param array $input * @param string $input['templateid'] * @param int $input['type'] */ function getTypeItemFieldNames(array $input): array { switch ($input['type']) { case ITEM_TYPE_ZABBIX: return ['interfaceid', 'delay']; case ITEM_TYPE_TRAPPER: return ['trapper_hosts']; case ITEM_TYPE_SIMPLE: return ['interfaceid', 'username', 'password', 'delay']; case ITEM_TYPE_INTERNAL: return ['delay']; case ITEM_TYPE_ZABBIX_ACTIVE: return ['delay']; case ITEM_TYPE_EXTERNAL: return ['interfaceid', 'delay']; case ITEM_TYPE_DB_MONITOR: return ['username', 'password', 'params', 'delay']; case ITEM_TYPE_IPMI: if ($input['templateid'] == 0) { return ['interfaceid', 'ipmi_sensor', 'delay']; } else { return ['interfaceid', 'delay']; } case ITEM_TYPE_SSH: return ['interfaceid', 'authtype', 'username', 'publickey', 'privatekey', 'password', 'params', 'delay']; case ITEM_TYPE_TELNET: return ['interfaceid', 'username', 'password', 'params', 'delay']; case ITEM_TYPE_CALCULATED: return ['params', 'delay']; case ITEM_TYPE_JMX: if ($input['templateid'] == 0) { return ['interfaceid', 'jmx_endpoint', 'username', 'password', 'delay']; } else { return ['interfaceid', 'username', 'password', 'delay']; } case ITEM_TYPE_SNMPTRAP: return ['interfaceid']; case ITEM_TYPE_DEPENDENT: if ($input['templateid'] == 0) { return ['master_itemid']; } return []; case ITEM_TYPE_HTTPAGENT: if ($input['templateid'] == 0) { return ['url', 'query_fields', 'request_method', 'post_type', 'posts', 'headers', 'status_codes', 'follow_redirects', 'retrieve_mode', 'output_format', 'http_proxy', 'interfaceid', 'authtype', 'username', 'password', 'verify_peer', 'verify_host', 'ssl_cert_file', 'ssl_key_file', 'ssl_key_password', 'timeout', 'delay', 'allow_traps', 'trapper_hosts' ]; } else { return ['interfaceid', 'delay', 'allow_traps', 'trapper_hosts']; } case ITEM_TYPE_SNMP: if ($input['templateid'] == 0) { return ['interfaceid', 'snmp_oid', 'delay']; } else { return ['interfaceid', 'delay']; } case ITEM_TYPE_SCRIPT: if ($input['templateid'] == 0) { return ['parameters', 'params', 'timeout', 'delay']; } else { return ['delay']; } } } /** * Get item field names excluding those that don't match a specific conditions. * * @param array $field_names * @param array $input * @param int $input['type'] * @param string $input['key_'] * @param int $input['value_type'] * @param int $input['authtype'] * @param int $input['allow_traps'] * @param int $input['hosts'][0]['status'] * * @return array */ function getConditionalItemFieldNames(array $field_names, array $input): array { return array_filter($field_names, static function ($field_name) use ($input): bool { switch ($field_name) { case 'units': case 'trends': return in_array($input['value_type'], [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64]); case 'valuemapid': return in_array($input['value_type'], [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_UINT64] ); case 'inventory_link': return in_array($input['value_type'], [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_UINT64, ITEM_VALUE_TYPE_TEXT] ); case 'logtimefmt': return $input['value_type'] == ITEM_VALUE_TYPE_LOG; case 'interfaceid': return in_array($input['hosts'][0]['status'], [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED]); case 'username': case 'password': return $input['type'] != ITEM_TYPE_HTTPAGENT || in_array($input['authtype'], [ZBX_HTTP_AUTH_BASIC, ZBX_HTTP_AUTH_NTLM, ZBX_HTTP_AUTH_KERBEROS, ZBX_HTTP_AUTH_DIGEST] ); case 'delay': return $input['type'] != ITEM_TYPE_ZABBIX_ACTIVE || strncmp($input['key_'], 'mqtt.get', 8) != 0; case 'trapper_hosts': return $input['type'] != ITEM_TYPE_HTTPAGENT || $input['allow_traps'] == HTTPCHECK_ALLOW_TRAPS_ON; case 'publickey': case 'privatekey': return $input['authtype'] == ITEM_AUTHTYPE_PUBLICKEY; } return true; }); } /** * Apply sorting for discovery rule filter or override filter conditions, if appropriate. * Prioritization by non/exist operator applied between matching macros. * * @param array $conditions * @param int $evaltype * * @return array */ function sortLldRuleFilterConditions(array $conditions, int $evaltype): array { switch ($evaltype) { case CONDITION_EVAL_TYPE_AND_OR: case CONDITION_EVAL_TYPE_AND: case CONDITION_EVAL_TYPE_OR: usort($conditions, static function(array $condition_a, array $condition_b): int { $comparison = strnatcasecmp($condition_a['macro'], $condition_b['macro']); if ($comparison != 0) { return $comparison; } $exist_operators = [CONDITION_OPERATOR_NOT_EXISTS, CONDITION_OPERATOR_EXISTS]; $comparison = (int) in_array($condition_b['operator'], $exist_operators) - (int) in_array($condition_a['operator'], $exist_operators); if ($comparison != 0) { return $comparison; } return strnatcasecmp($condition_a['value'], $condition_b['value']); }); foreach ($conditions as $i => &$condition) { $condition['formulaid'] = num2letter($i); } unset($condition); break; case CONDITION_EVAL_TYPE_EXPRESSION: CArrayHelper::sort($conditions, ['formulaid']); break; } return array_values($conditions); }