<?php /* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ class CMacrosResolverGeneral { /** * Interface priorities. * * @var array */ protected const interfacePriorities = [ INTERFACE_TYPE_AGENT => 4, INTERFACE_TYPE_SNMP => 3, INTERFACE_TYPE_JMX => 2, INTERFACE_TYPE_IPMI => 1 ]; protected const aggr_triggers_macros = ['{TRIGGER.EVENTS.ACK}', '{TRIGGER.EVENTS.PROBLEM.ACK}', '{TRIGGER.EVENTS.PROBLEM.UNACK}', '{TRIGGER.EVENTS.UNACK}', '{TRIGGER.PROBLEM.EVENTS.PROBLEM.ACK}', '{TRIGGER.PROBLEM.EVENTS.PROBLEM.UNACK}', '{TRIGGERS.UNACK}', '{TRIGGERS.PROBLEM.UNACK}', '{TRIGGERS.ACK}', '{TRIGGERS.PROBLEM.ACK}']; /** * Get reference macros for trigger. * If macro reference non existing value it expands to empty string. * * @param string $expression * @param array $references * * @return array */ protected static function resolveTriggerReferences($expression, $references) { $values = []; $expression_parser = new CExpressionParser([ 'usermacros' => true, 'lldmacros' => true, 'collapsed_expression' => true ]); if ($expression_parser->parse($expression) == CParser::PARSE_SUCCESS) { foreach ($expression_parser->getResult()->getTokens() as $token) { switch ($token['type']) { case CExpressionParserResult::TOKEN_TYPE_NUMBER: case CExpressionParserResult::TOKEN_TYPE_USER_MACRO: $values[] = $token['match']; break; case CExpressionParserResult::TOKEN_TYPE_STRING: $values[] = CExpressionParser::unquoteString($token['match']); break; } } } foreach ($references as $macro => $value) { $i = (int) $macro[1] - 1; $references[$macro] = array_key_exists($i, $values) ? $values[$i] : ''; } return $references; } /** * Checking existence of the macros. * * @param array $texts * @param array $type * * @return bool */ protected function hasMacros(array $texts, array $types) { foreach ($texts as $text) { if (self::getMacroPositions($text, $types)) { return true; } } return false; } /** * Transform types, used in extractMacros() function to types which can be used in getMacroPositions(). * * @param array $types * * @return array */ protected static function transformToPositionTypes(array $types) { foreach (['macros', 'macros_n', 'macros_an'] as $type) { if (array_key_exists($type, $types)) { $patterns = []; foreach ($types[$type] as $key => $_patterns) { $patterns = array_merge($patterns, $_patterns); } $types[$type] = $patterns; } } return $types; } /** * Extract positions of the macros from a string. * * @param string $text * @param array $types * @param bool $types['usermacros'] * @param array $types['macros'][<macro_patterns>] * @param array $types['macros_n'][<macro_patterns>] * @param array $types['macros_an'][<macro_patterns>] * @param bool $types['references'] * @param bool $types['lldmacros'] * @param bool $types['functionids'] * * @return array */ public static function getMacroPositions($text, array $types) { $macros = []; $macro_parsers = []; if (array_key_exists('usermacros', $types)) { array_push($macro_parsers, new CUserMacroParser, new CUserMacroFunctionParser); } if (array_key_exists('macros', $types)) { $options = ['macros' => $types['macros']]; array_push($macro_parsers, new CMacroParser($options), new CMacroFunctionParser($options)); } if (array_key_exists('macros_n', $types)) { $options = ['macros' => $types['macros_n'], 'ref_type' => CMacroParser::REFERENCE_NUMERIC]; array_push($macro_parsers, new CMacroParser($options), new CMacroFunctionParser($options)); } if (array_key_exists('macros_an', $types)) { $options = ['macros' => $types['macros_an'], 'ref_type' => CMacroParser::REFERENCE_ALPHANUMERIC]; array_push($macro_parsers, new CMacroParser($options), new CMacroFunctionParser($options)); } if (array_key_exists('references', $types)) { $macro_parsers[] = new CReferenceParser; } if (array_key_exists('lldmacros', $types)) { array_push($macro_parsers, new CLLDMacroParser, new CLLDMacroFunctionParser); } if (array_key_exists('functionids', $types)) { $macro_parsers[] = new CFunctionIdParser(); } for ($pos = 0; isset($text[$pos]); $pos++) { foreach ($macro_parsers as $macro_parser) { if ($macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { $macros[$pos] = $macro_parser->getMatch(); $pos += $macro_parser->getLength() - 1; break; } } } return $macros; } /** * Returns true if parsed expression is calculable. * * @param array $tokens * * @return bool */ private static function isCalculableExpression(array $tokens): bool { if (count($tokens) != 1 || $tokens[0]['type'] != CExpressionParserResult::TOKEN_TYPE_HIST_FUNCTION) { return false; } $expression_validator = new CExpressionValidator(); if (!$expression_validator->validate($tokens)) { return false; } if (!in_array($tokens[0]['data']['function'], ['last', 'min', 'max', 'avg'])) { return false; } $parameters = $tokens[0]['data']['parameters']; // Time shift is not supported. if (array_key_exists(1, $parameters) && ($parameters[1]['type'] != CHistFunctionParser::PARAM_TYPE_PERIOD || $parameters[1]['data']['sec_num'][0] === '#' || $parameters[1]['data']['time_shift'] !== '')) { return false; } return true; } /** * Extract macros from a string. * * @param array $texts * @param array $types * @param bool $types['usermacros'] Extract user macros. * For example, "{$MACRO}", "{{$MACRO}.func(param)}". * @param array $types['macros'][][<macro_patterns>] Extract macros with optional macro function. * For example, "{HOST.HOST}", * "{{ITEM.VALUE}.func(param)}". * @param array $types['macros_n'][][<macro_patterns>] Extract macros with optional numeric index and macro * function. * For example, "{HOST.HOST<1-9>}", * "{{ITEM.VALUE<1-9>}.func(param)}". * @param array $types['macros_an'][][<macro_patterns>] Extract macros with optional alphanumeric index. * For example, "{EVENT.TAGS.Service}", * {{EVENT.TAGS.Service}.func(param)}" * @param bool $types['references'] Extract dollar-sign references. For example, "$5". * @param bool $types['lldmacros'] Extract low-level discovery macros. * For example, "{#LLD}", {{#LLD}.func(param)}. * @param bool $types['functionids'] Extract numeric macros. For example, "{12345}". * @param bool $types['expr_macros'] Extract expression macros. * For example, "{?func(/host/key, param)}", * "{{?func(/host/key, param)}.func(param)}" * @param bool $types['expr_macros_host'] Extract expression macros with the ability to * specify a {HOST.HOST} macro or an empty host name * instead of a hostname. * For example, * "{?func(/host/key, param)}", * "{?func(/{HOST.HOST}/key, param)}", * "{?func(//key, param)}", * "{{?func(/host/key, param)}.func(param)}". * @param bool $types['expr_macros_host_n'] Extract expression macros with the ability to * specify a {HOST.HOST<1-9>} macro or an empty host * name instead of a hostname. * For example, * "{?func(/host/key, param)}", * "{?func(/{HOST.HOST}/key, param)}", * "{?func(/{HOST.HOST5}/key, param)}", * "{?func(//key, param)}", * "{{?func(/host/key, param)}.func(param)}". * * @return array */ public static function extractMacros(array $texts, array $types) { $macros = []; $extract_usermacros = array_key_exists('usermacros', $types); $extract_macros = array_key_exists('macros', $types); $extract_macros_n = array_key_exists('macros_n', $types); $extract_macros_an = array_key_exists('macros_an', $types); $extract_references = array_key_exists('references', $types); $extract_lldmacros = array_key_exists('lldmacros', $types); $extract_functionids = array_key_exists('functionids', $types); $extract_expr_macros = array_key_exists('expr_macros', $types); $extract_expr_macros_host = array_key_exists('expr_macros_host', $types); $extract_expr_macros_host_n = array_key_exists('expr_macros_host_n', $types); $macro_parsers_by_type = []; $macro_function_parsers_by_type = []; if ($extract_usermacros) { $macros['usermacros'] = []; $user_macro_parser = new CUserMacroParser(); $user_macro_function_parser = new CUserMacroFunctionParser(); } if ($extract_macros) { $macros['macros'] = []; foreach ($types['macros'] as $key => $macro_patterns) { $options = ['macros' => $macro_patterns]; $macro_parsers_by_type['macros'][$key] = new CMacroParser($options); $macro_function_parsers_by_type['macros'][$key] = new CMacroFunctionParser($options); $macros['macros'][$key] = []; } } if ($extract_macros_n) { $macros['macros_n'] = []; foreach ($types['macros_n'] as $key => $macro_patterns) { $options = ['macros' => $macro_patterns, 'ref_type' => CMacroParser::REFERENCE_NUMERIC]; $macro_parsers_by_type['macros_n'][$key] = new CMacroParser($options); $macro_function_parsers_by_type['macros_n'][$key] = new CMacroFunctionParser($options); $macros['macros_n'][$key] = []; } } if ($extract_macros_an) { $macros['macros_an'] = []; foreach ($types['macros_an'] as $key => $macro_patterns) { $options = ['macros' => $macro_patterns, 'ref_type' => CMacroParser::REFERENCE_ALPHANUMERIC]; $macro_parsers_by_type['macros_an'][$key] = new CMacroParser($options); $macro_function_parsers_by_type['macros_an'][$key] = new CMacroFunctionParser($options); $macros['macros_an'][$key] = []; } } if ($extract_references) { $macros['references'] = []; $reference_parser = new CReferenceParser(); } if ($extract_lldmacros) { $macros['lldmacros'] = []; $lld_macro_parser = new CLLDMacroParser(); $lld_macro_function_parser = new CLLDMacroFunctionParser(); } if ($extract_functionids) { $macros['functionids'] = []; $functionid_parser = new CFunctionIdParser(); } if ($extract_expr_macros) { $macros['expr_macros'] = []; $expr_macro_parser = new CExpressionMacroParser(); $expr_macro_function_parser = new CExpressionMacroFunctionParser(); } if ($extract_expr_macros_host) { $macros['expr_macros_host'] = []; $options = ['host_macro' => true, 'empty_host' => true]; $expr_macro_parser_host = new CExpressionMacroParser($options); $expr_macro_function_parser_host = new CExpressionMacroFunctionParser($options); } if ($extract_expr_macros_host_n) { $macros['expr_macros_host_n'] = []; $options = ['host_macro_n' => true, 'empty_host' => true]; $expr_macro_parser_host_n = new CExpressionMacroParser($options); $expr_macro_function_parser_host_n = new CExpressionMacroFunctionParser($options); } foreach ($texts as $text) { for ($pos = 0; isset($text[$pos]); $pos++) { if ($extract_usermacros) { if ($user_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { $macros['usermacros'][$user_macro_parser->getMatch()] = [ 'macro' => $user_macro_parser->getMacro(), 'context' => $user_macro_parser->getContext() ]; $pos += $user_macro_parser->getLength() - 1; continue; } if ($user_macro_function_parser->parse($text, $pos) != CParser::PARSE_FAIL) { $user_macro_parser = $user_macro_function_parser->getUserMacroParser(); $function_parser = $user_macro_function_parser->getFunctionParser(); $macros['usermacros'][$user_macro_function_parser->getMatch()] = [ 'macro' => $user_macro_parser->getMacro(), 'context' => $user_macro_parser->getContext(), 'macrofunc' => [ 'function' => $function_parser->getFunction(), 'parameters' => $function_parser->getParams() ] ]; $pos += $user_macro_function_parser->getLength() - 1; continue; } } foreach ($macro_parsers_by_type as $type => $macro_parsers) { foreach ($macro_parsers as $key => $macro_parser) { if ($macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { $macros[$type][$key][$macro_parser->getMatch()] = [ 'macro' => $macro_parser->getMacro(), 'f_num' => $macro_parser->getReference() ]; $pos += $macro_parser->getLength() - 1; continue 2; } } } foreach ($macro_function_parsers_by_type as $type => $macro_function_parsers) { foreach ($macro_function_parsers as $key => $macro_function_parser) { if ($macro_function_parser->parse($text, $pos) != CParser::PARSE_FAIL) { $macro_parser = $macro_function_parser->getMacroParser(); $function_parser = $macro_function_parser->getFunctionParser(); $macros[$type][$key][$macro_function_parser->getMatch()] = [ 'macro' => $macro_parser->getMacro(), 'f_num' => $macro_parser->getReference(), 'macrofunc' => [ 'function' => $function_parser->getFunction(), 'parameters' => $function_parser->getParams() ] ]; $pos += $macro_function_parser->getLength() - 1; continue 2; } } } if ($extract_references && $reference_parser->parse($text, $pos) != CParser::PARSE_FAIL) { $macros['references'][$reference_parser->getMatch()] = null; $pos += $reference_parser->getLength() - 1; continue; } if ($extract_lldmacros) { if ($lld_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { $macros['lldmacros'][$lld_macro_parser->getMatch()] = null; $pos += $lld_macro_parser->getLength() - 1; continue; } elseif ($lld_macro_function_parser->parse($text, $pos) != CParser::PARSE_FAIL) { $macros['lldmacros'][$lld_macro_function_parser->getMatch()] = null; $pos += $lld_macro_function_parser->getLength() - 1; continue; } } if ($extract_functionids && $functionid_parser->parse($text, $pos) != CParser::PARSE_FAIL) { $macros['functionids'][$functionid_parser->getMatch()] = null; $pos += $functionid_parser->getLength() - 1; continue; } if ($extract_expr_macros && $expr_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { $tokens = $expr_macro_parser ->getExpressionParser() ->getResult() ->getTokens(); if (self::isCalculableExpression($tokens)) { $macros['expr_macros'][$expr_macro_parser->getMatch()] = [ 'function' => $tokens[0]['data']['function'], 'host' => $tokens[0]['data']['parameters'][0]['data']['host'], 'key' => $tokens[0]['data']['parameters'][0]['data']['item'], 'sec_num' => array_key_exists(1, $tokens[0]['data']['parameters']) ? $tokens[0]['data']['parameters'][1]['data']['sec_num'] : '' ]; $pos += $expr_macro_parser->getLength() - 1; continue; } } if ($extract_expr_macros && $expr_macro_function_parser->parse($text, $pos) != CParser::PARSE_FAIL) { $tokens = $expr_macro_function_parser ->getExpressionMacroParser() ->getExpressionParser() ->getResult() ->getTokens(); if (self::isCalculableExpression($tokens)) { $function_parser = $expr_macro_function_parser->getFunctionParser(); $macros['expr_macros'][$expr_macro_function_parser->getMatch()] = [ 'function' => $tokens[0]['data']['function'], 'host' => $tokens[0]['data']['parameters'][0]['data']['host'], 'key' => $tokens[0]['data']['parameters'][0]['data']['item'], 'sec_num' => array_key_exists(1, $tokens[0]['data']['parameters']) ? $tokens[0]['data']['parameters'][1]['data']['sec_num'] : '', 'macrofunc' => [ 'function' => $function_parser->getFunction(), 'parameters' => $function_parser->getParams() ] ]; $pos += $expr_macro_function_parser->getLength() - 1; continue; } } if ($extract_expr_macros_host && $expr_macro_parser_host->parse($text, $pos) != CParser::PARSE_FAIL) { $tokens = $expr_macro_parser_host ->getExpressionParser() ->getResult() ->getTokens(); if (self::isCalculableExpression($tokens)) { $macros['expr_macros_host'][$expr_macro_parser_host->getMatch()] = [ 'function' => $tokens[0]['data']['function'], 'host' => $tokens[0]['data']['parameters'][0]['data']['host'], 'key' => $tokens[0]['data']['parameters'][0]['data']['item'], 'sec_num' => array_key_exists(1, $tokens[0]['data']['parameters']) ? $tokens[0]['data']['parameters'][1]['data']['sec_num'] : '' ]; $pos += $expr_macro_parser_host->getLength() - 1; continue; } } if ($extract_expr_macros_host && $expr_macro_function_parser_host->parse($text, $pos) != CParser::PARSE_FAIL) { $tokens = $expr_macro_function_parser_host ->getExpressionMacroParser() ->getExpressionParser() ->getResult() ->getTokens(); if (self::isCalculableExpression($tokens)) { $function_parser = $expr_macro_function_parser_host->getFunctionParser(); $macros['expr_macros_host'][$expr_macro_function_parser_host->getMatch()] = [ 'function' => $tokens[0]['data']['function'], 'host' => $tokens[0]['data']['parameters'][0]['data']['host'], 'key' => $tokens[0]['data']['parameters'][0]['data']['item'], 'sec_num' => array_key_exists(1, $tokens[0]['data']['parameters']) ? $tokens[0]['data']['parameters'][1]['data']['sec_num'] : '', 'macrofunc' => [ 'function' => $function_parser->getFunction(), 'parameters' => $function_parser->getParams() ] ]; $pos += $expr_macro_function_parser_host->getLength() - 1; continue; } } if ($extract_expr_macros_host_n && $expr_macro_parser_host_n->parse($text, $pos) != CParser::PARSE_FAIL) { $tokens = $expr_macro_parser_host_n ->getExpressionParser() ->getResult() ->getTokens(); if (self::isCalculableExpression($tokens)) { $macros['expr_macros_host_n'][$expr_macro_parser_host_n->getMatch()] = [ 'function' => $tokens[0]['data']['function'], 'host' => $tokens[0]['data']['parameters'][0]['data']['host'], 'key' => $tokens[0]['data']['parameters'][0]['data']['item'], 'sec_num' => array_key_exists(1, $tokens[0]['data']['parameters']) ? $tokens[0]['data']['parameters'][1]['data']['sec_num'] : '' ]; $pos += $expr_macro_parser_host_n->getLength() - 1; continue; } } if ($extract_expr_macros_host_n && $expr_macro_function_parser_host_n->parse($text, $pos) != CParser::PARSE_FAIL) { $tokens = $expr_macro_function_parser_host_n ->getExpressionMacroParser() ->getExpressionParser() ->getResult() ->getTokens(); if (self::isCalculableExpression($tokens)) { $function_parser = $expr_macro_function_parser_host_n->getFunctionParser(); $macros['expr_macros_host_n'][$expr_macro_function_parser_host_n->getMatch()] = [ 'function' => $tokens[0]['data']['function'], 'host' => $tokens[0]['data']['parameters'][0]['data']['host'], 'key' => $tokens[0]['data']['parameters'][0]['data']['item'], 'sec_num' => array_key_exists(1, $tokens[0]['data']['parameters']) ? $tokens[0]['data']['parameters'][1]['data']['sec_num'] : '', 'macrofunc' => [ 'function' => $function_parser->getFunction(), 'parameters' => $function_parser->getParams() ] ]; $pos += $expr_macro_function_parser_host_n->getLength() - 1; continue; } } } } return $macros; } /** * Returns the list of the item key parameters. * * @param array $params_raw * * @return array */ public static function getItemKeyParameters($params_raw) { $item_key_parameters = []; foreach ($params_raw as $param_raw) { switch ($param_raw['type']) { case CItemKey::PARAM_ARRAY: $item_key_parameters = array_merge($item_key_parameters, self::getItemKeyParameters($param_raw['parameters']) ); break; case CItemKey::PARAM_UNQUOTED: $item_key_parameters[] = $param_raw['raw']; break; case CItemKey::PARAM_QUOTED: $item_key_parameters[] = CItemKey::unquoteParam($param_raw['raw']); break; } } return $item_key_parameters; } /** * Extract macros from an item key. * * @param string $key an item key * @param array $types the types of macros (see extractMacros() for more details) * * @return array see extractMacros() for more details */ protected static function extractItemKeyMacros($key, array $types) { $item_key_parser = new CItemKey(); $item_key_parameters = []; if ($item_key_parser->parse($key) == CParser::PARSE_SUCCESS) { $item_key_parameters = self::getItemKeyParameters($item_key_parser->getParamsRaw()); } return self::extractMacros($item_key_parameters, $types); } /** * Extract macros from a trigger function. * * @param string $function a history function, for example 'last(/host/key, {$OFFSET})' * @param array $types the types of macros (see extractMacros() for more details) * * @return array see extractMacros() for more details */ protected static function extractFunctionMacros($function, array $types) { $hist_function_parser = new CHistFunctionParser(['usermacros' => true, 'lldmacros' => true]); $function_parameters = []; if ($hist_function_parser->parse($function) == CParser::PARSE_SUCCESS) { foreach ($hist_function_parser->getParameters() as $parameter) { switch ($parameter['type']) { case CHistFunctionParser::PARAM_TYPE_PERIOD: case CHistFunctionParser::PARAM_TYPE_UNQUOTED: $function_parameters[] = $parameter['match']; break; case CHistFunctionParser::PARAM_TYPE_QUOTED: $function_parameters[] = CHistFunctionParser::unquoteParam($parameter['match']); break; } } } return self::extractMacros($function_parameters, $types); } /** * Resolves macros in the item key parameters. * * @param string $key_chain an item key chain * @param array $params_raw * @param array $values the list of macros (['{<MACRO>}' => '<value>', ...]) * * @return string */ private static function resolveItemKeyParamsMacros($key_chain, array $params_raw, array $values) { foreach (array_reverse($params_raw) as $param_raw) { $param = $param_raw['raw']; $forced = false; switch ($param_raw['type']) { case CItemKey::PARAM_ARRAY: $param = self::resolveItemKeyParamsMacros($param, $param_raw['parameters'], $values); break; case CItemKey::PARAM_QUOTED: $param = CItemKey::unquoteParam($param); $forced = true; // break; is not missing here case CItemKey::PARAM_UNQUOTED: $param = quoteItemKeyParam(strtr($param, $values), $forced); break; } $key_chain = substr_replace($key_chain, $param, $param_raw['pos'], strlen($param_raw['raw'])); } return $key_chain; } /** * Resolves macros in the item key. * * @param string $key An item key. * @param array $values The list of macros (['{<MACRO>}' => '<value>', ...]). * * @return string */ public static function resolveItemKeyMacros($key, array $values) { $item_key_parser = new CItemKey(); if ($item_key_parser->parse($key) == CParser::PARSE_SUCCESS) { $key = self::resolveItemKeyParamsMacros($key, $item_key_parser->getParamsRaw(), $values); } return $key; } /** * Resolves macros in the trigger function parameters. * * @param string $function a trigger function * @param array $macros the list of macros (['{<MACRO>}' => '<value>', ...]) * * @return string */ protected static function resolveFunctionMacros($function, array $macros) { $hist_function_parser = new CHistFunctionParser(['usermacros' => true, 'lldmacros' => true]); if ($hist_function_parser->parse($function) == CParser::PARSE_SUCCESS) { foreach (array_reverse($hist_function_parser->getParameters(), true) as $i => $parameter) { switch ($parameter['type']) { case CHistFunctionParser::PARAM_TYPE_PERIOD: case CHistFunctionParser::PARAM_TYPE_UNQUOTED: case CHistFunctionParser::PARAM_TYPE_QUOTED: $param = strtr($hist_function_parser->getParam($i), $macros); if ($parameter['type'] != CHistFunctionParser::PARAM_TYPE_PERIOD) { $force = $parameter['type'] == CHistFunctionParser::PARAM_TYPE_QUOTED; $param = CHistFunctionParser::quoteParam($param, $force, ['usermacros' => true, 'lldmacros' => true] ); } $function = substr_replace($function, $param, $parameter['pos'], $parameter['length']); break; } } } return $function; } /** * Find function ids in trigger expression. * * @param string $expression * * @return array where key is function id position in expression and value is function id */ protected static function findFunctions($expression) { $functionids = []; $expression_parser = new CExpressionParser(['usermacros' => true, 'collapsed_expression' => true]); if ($expression_parser->parse($expression) == CParser::PARSE_SUCCESS) { $tokens = $expression_parser ->getResult() ->getTokensOfTypes([CExpressionParserResult::TOKEN_TYPE_FUNCTIONID_MACRO]); foreach ($tokens as $f_num => $token) { $functionids[$f_num + 1] = substr($token['match'], 1, -1); // strip curly braces } } if (array_key_exists(1, $functionids)) { $functionids[0] = $functionids[1]; } return $functionids; } /** * Get interface macros. * * @param array $macros * @param array $macros[<functionid>] * @param array $macros[<functionid>][<macro>] an array of the tokens * @param array $macro_values * * @return array */ protected static function getIpMacros(array $macros, array $macro_values) { if (!$macros) { return $macro_values; } $result = DBselect( 'SELECT f.triggerid,f.functionid,n.ip,n.dns,n.type,n.useip,n.port'. ' FROM functions f'. ' JOIN items i ON f.itemid=i.itemid'. ' JOIN interface n ON i.hostid=n.hostid'. ' WHERE '.dbConditionInt('f.functionid', array_keys($macros)). ' AND n.main=1' ); // Macro should be resolved to interface with highest priority ($priorities). $interfaces = []; while ($row = DBfetch($result)) { if (array_key_exists($row['functionid'], $interfaces) && self::interfacePriorities[$interfaces[$row['functionid']]['type']] > self::interfacePriorities[$row['type']]) { continue; } $interfaces[$row['functionid']] = $row; } foreach ($interfaces as $interface) { foreach ($macros[$interface['functionid']] as $macro => $tokens) { switch ($macro) { case 'IPADDRESS': case 'HOST.IP': $value = $interface['ip']; break; case 'HOST.DNS': $value = $interface['dns']; break; case 'HOST.CONN': $value = $interface['useip'] ? $interface['ip'] : $interface['dns']; break; case 'HOST.PORT': $value = $interface['port']; break; } foreach ($tokens as $token) { $macro_values[$interface['triggerid']][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Resolves items value maps, valuemap property will be added to every item. * * @param array $items * @param int $items[]['itemid'] * @param int $items[]['valuemapid'] * * @return array */ protected static function getItemsValueMaps(array $items): array { foreach ($items as &$item) { $item['valuemap'] = []; } unset($item); $valuemapids = array_flip(array_column($items, 'valuemapid')); unset($valuemapids[0]); if (!$valuemapids) { return $items; } $options = [ 'output' => ['valuemapid', 'type', 'value', 'newvalue'], 'filter' => ['valuemapid' => array_keys($valuemapids)], 'sortfield' => ['sortorder'] ]; $db_mappings = DBselect(DB::makeSql('valuemap_mapping', $options)); $db_valuemaps = []; while ($db_mapping = DBfetch($db_mappings)) { $db_valuemaps[$db_mapping['valuemapid']]['mappings'][] = [ 'type' => $db_mapping['type'], 'value' => $db_mapping['value'], 'newvalue' => $db_mapping['newvalue'] ]; } foreach ($items as &$item) { if (array_key_exists($item['valuemapid'], $db_valuemaps)) { $item['valuemap'] = $db_valuemaps[$item['valuemapid']]; } } unset($item); return $items; } /** * Get item macros by itemid. * * @param array $macros * @param array $macros[<itemid>] * @param array $macros[<itemid>][<macro>] * @param array $macro_values * @param array $macro_values[<itemid>] * @param string $macro_values[<itemid>][<token>] * * @return array */ protected static function getItemMacrosByItemId(array $macros, array $macro_values) { if (!$macros) { return $macro_values; } $db_items = API::Item()->get([ 'output' => ['itemid', 'hostid', 'name', 'name_resolved', 'key_', 'value_type', 'state', 'description'], 'itemids' => array_keys($macros), 'webitems' => true, 'preservekeys' => true ]); $db_items = CMacrosResolverHelper::resolveItemKeys($db_items); $db_items = CMacrosResolverHelper::resolveItemDescriptions($db_items); foreach ($db_items as &$db_item) { $db_item['state'] = itemState($db_item['state']); } unset($db_item); $item_macros = ['ITEM.DESCRIPTION' => 'description_expanded', 'ITEM.DESCRIPTION.ORIG' => 'description', 'ITEM.ID' => 'itemid', 'ITEM.KEY' => 'key_expanded', 'ITEM.KEY.ORIG' => 'key_', 'ITEM.NAME' => 'name_resolved', 'ITEM.NAME.ORIG' => 'name', 'ITEM.STATE' => 'state', 'ITEM.VALUETYPE' => 'value_type' ]; foreach ($db_items as $itemid => $db_item) { foreach ($macros[$itemid] as $macro => $tokens) { $value = $db_item[$item_macros[$macro]]; foreach ($tokens as $token) { $macro_values[$itemid][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Get item value macros by itemid. * * @param array $macros * @param array $macros[<itemid>] * @param array $macros[<itemid>][<macro>] * @param array $macros[<itemid>][<macro>][] * @param string $macros[<itemid>][<macro>][]['token'] * @param array $macros[<itemid>][<macro>][]['macrofunc'] * @param array $macro_values * * @return array */ protected static function getItemValueMacrosByItemId(array $macros, array $macro_values) { if (!$macros) { return $macro_values; } $db_items = API::Item()->get([ 'output' => ['itemid', 'value_type', 'units', 'valuemapid'], 'itemids' => array_keys($macros), 'webitems' => true, 'preservekeys' => true ]); $db_items = self::getItemsValueMaps($db_items); $history = Manager::History()->getLastValues($db_items, 1, timeUnitToSeconds( CSettingsHelper::get(CSettingsHelper::HISTORY_PERIOD) )); foreach ($history as $itemid => $item_history) { foreach ($macros[$itemid] as $macro => $tokens) { if ($macro === 'ITEM.VALUE' || $macro === 'ITEM.LASTVALUE') { $value = $item_history[0]['value']; foreach ($tokens as $token) { $macro_values[$itemid][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : formatHistoryValue($value, $db_items[$itemid]); } } elseif ($db_items[$itemid]['value_type'] == ITEM_VALUE_TYPE_LOG) { switch ($macro) { case 'ITEM.LOG.DATE': $value = date('Y.m.d', $history[$itemid][0]['timestamp']); break; case 'ITEM.LOG.TIME': $value = date('H:i:s', $history[$itemid][0]['timestamp']); break; case 'ITEM.LOG.AGE': $value = zbx_date2age($history[$itemid][0]['timestamp']); break; case 'ITEM.LOG.SOURCE': $value = $history[$itemid][0]['source']; break; case 'ITEM.LOG.SEVERITY': $value = get_item_logtype_description($history[$itemid][0]['severity']); break; case 'ITEM.LOG.NSEVERITY': $value = $history[$itemid][0]['severity']; break; case 'ITEM.LOG.EVENTID': $value = $history[$itemid][0]['logeventid']; break; } foreach ($tokens as $token) { $macro_values[$itemid][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } } return $macro_values; } /** * Get inventory macros by itemid. * * @param array $macros * @param array $macros[<itemid>] * @param array $macros[<itemid>][<macro>] * @param array $macro_values * @param array $macro_values[<itemid>] * @param string $macro_values[<itemid>][<token>] * * @return array */ protected static function getInventoryMacrosByItemId(array $macros, array $macro_values): array { if (!$macros) { return $macro_values; } $db_items = API::Item()->get([ 'output' => ['hostid'], 'itemids' => array_keys($macros), 'preservekeys' => true ]); if (!$db_items) { return $macro_values; } $inventory_macros = self::getSupportedHostInventoryMacrosMap(); $db_hosts = API::Host()->get([ 'output' => ['inventory_mode'], 'selectInventory' => array_values($inventory_macros), 'hostids' => array_unique(array_column($db_items, 'hostid')), 'preservekeys' => true ]); foreach ($db_items as $itemid => $db_item) { if (!array_key_exists($db_item['hostid'], $db_hosts) || $db_hosts[$db_item['hostid']]['inventory_mode'] == HOST_INVENTORY_DISABLED) { continue; } foreach ($macros[$itemid] as $macro => $tokens) { $value = $db_hosts[$db_item['hostid']]['inventory'][$inventory_macros['{'.$macro.'}']]; foreach ($tokens as $token) { $macro_values[$itemid][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Get item macros. * * @param array $macros * @param array $macros[<functionid>] * @param array $macros[<functionid>][<macro>] An array of the tokens. * @param array $macro_values * @param array $triggers * @param array $options * @param bool $options['events'] Resolve {ITEM.VALUE} macro using 'clock' and 'ns' fields. * @param bool $options['html'] * * @return array */ protected static function getItemMacros(array $macros, array $macro_values, array $triggers = [], array $options = []) { if (!$macros) { return $macro_values; } $options += [ 'events' => false, 'html' => false ]; $functions = DBfetchArray(DBselect( 'SELECT f.triggerid,f.functionid,i.itemid,i.name,i.value_type,i.units,i.valuemapid'. ' FROM functions f'. ' JOIN items i ON f.itemid=i.itemid'. ' JOIN hosts h ON i.hostid=h.hostid'. ' WHERE '.dbConditionInt('f.functionid', array_keys($macros)) )); $functions = self::getItemsValueMaps($functions); // False passed to DBfetch to get data without null converted to 0, which is done by default. foreach ($functions as $function) { foreach ($macros[$function['functionid']] as $m => $tokens) { $clock = null; $value = null; switch ($m) { case 'ITEM.VALUE': if ($options['events']) { $trigger = $triggers[$function['triggerid']]; $history = Manager::History()->getValueAt($function, $trigger['clock'], $trigger['ns']); if (is_array($history)) { if (array_key_exists('clock', $history)) { $clock = $history['clock']; } if (array_key_exists('value', $history) && $function['value_type'] != ITEM_VALUE_TYPE_BINARY) { $value = $history['value']; } } break; } // break; is not missing here case 'ITEM.LASTVALUE': $history = Manager::History()->getLastValues([$function], 1, timeUnitToSeconds( CSettingsHelper::get(CSettingsHelper::HISTORY_PERIOD) )); if (array_key_exists($function['itemid'], $history)) { $clock = $history[$function['itemid']][0]['clock']; if ($function['value_type'] != ITEM_VALUE_TYPE_BINARY) { $value = $history[$function['itemid']][0]['value']; } } break; } foreach ($tokens as $token) { if ($value !== null) { $macro_value = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : formatHistoryValue($value, $function); } else { $macro_value = UNRESOLVED_MACRO_STRING; } if ($options['html']) { $macro_value = str_replace(["\r\n", "\n"], [" "], $macro_value); $hint_table = (new CTable()) ->addClass(ZBX_STYLE_LIST_TABLE) ->addRow([ new CCol($function['name']), new CCol( ($clock !== null) ? zbx_date2str(DATE_TIME_FORMAT_SECONDS, $clock) : UNRESOLVED_MACRO_STRING ), new CCol($macro_value), new CCol( ($function['value_type'] == ITEM_VALUE_TYPE_FLOAT || $function['value_type'] == ITEM_VALUE_TYPE_UINT64) ? new CLink(_('Graph'), (new CUrl('history.php')) ->setArgument('action', HISTORY_GRAPH) ->setArgument('itemids[]', $function['itemid']) ->getUrl() ) : new CLink(_('History'), (new CUrl('history.php')) ->setArgument('action', HISTORY_VALUES) ->setArgument('itemids[]', $function['itemid']) ->getUrl() ) ) ]); $macro_value = new CSpan([ (new CSpan()) ->addClass('main-hint') ->setHint($hint_table), (new CLinkAction($macro_value)) ->addClass('hint-item') ->setAttribute('data-hintbox', '1') ]); } $macro_values[$function['triggerid']][$token['token']] = $macro_value; } } } return $macro_values; } protected static function getItemLogMacros(array $macros, array $macro_values) { if (!$macros) { return $macro_values; } $functions = DBfetchArray(DBselect( 'SELECT f.triggerid,f.functionid,i.itemid,i.value_type'. ' FROM functions f'. ' JOIN items i ON f.itemid=i.itemid'. ' JOIN hosts h ON i.hostid=h.hostid'. ' WHERE '.dbConditionInt('f.functionid', array_keys($macros)). ' AND i.value_type='.ITEM_VALUE_TYPE_LOG )); if (!$functions) { return $macro_values; } foreach ($functions as $function) { foreach ($macros[$function['functionid']] as $m => $tokens) { $value = UNRESOLVED_MACRO_STRING; $history = Manager::History()->getLastValues([$function], 1, timeUnitToSeconds( CSettingsHelper::get(CSettingsHelper::HISTORY_PERIOD) )); if (!array_key_exists($function['itemid'], $history)) { continue; } switch ($m) { case 'ITEM.LOG.DATE': $value = date('Y.m.d', $history[$function['itemid']][0]['timestamp']); break; case 'ITEM.LOG.TIME': $value = date('H:i:s', $history[$function['itemid']][0]['timestamp']); break; case 'ITEM.LOG.AGE': $value = zbx_date2age($history[$function['itemid']][0]['timestamp']); break; case 'ITEM.LOG.SOURCE': $value = $history[$function['itemid']][0]['source']; break; case 'ITEM.LOG.SEVERITY': $value = get_item_logtype_description($history[$function['itemid']][0]['severity']); break; case 'ITEM.LOG.NSEVERITY': $value = $history[$function['itemid']][0]['severity']; break; case 'ITEM.LOG.EVENTID': $value = $history[$function['itemid']][0]['logeventid']; break; } foreach ($tokens as $token) { $macro_values[$function['triggerid']][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Get macros with values. * * @param array $usermacros * @param array $usermacros[<triggerid>]['macros'] The list of user macros to resolve, * ['<usermacro1>' => null, ...]. * * @return array */ protected static function getTriggerUserMacros(array $usermacros, array $macro_values) { if (!$usermacros) { return $macro_values; } $db_triggers = API::Trigger()->get([ 'output' => [], 'selectHosts' => ['hostid'], 'triggerids' => array_keys($usermacros), 'preservekeys' => true ]); foreach ($usermacros as $triggerid => &$usermacros_data) { if (array_key_exists($triggerid, $db_triggers)) { $usermacros_data['hostids'] = array_unique(array_column($db_triggers[$triggerid]['hosts'], 'hostid')); } } unset($usermacros_data); return self::getUserMacros($usermacros, $macro_values); } /** * Get host macros. * * @param array $macros * @param array $macros[<functionid>] * @param array $macros[<functionid>][<macro>] an array of the tokens * @param array $macro_values * * @return array */ protected static function getHostMacros(array $macros, array $macro_values) { if (!$macros) { return $macro_values; } $result = DBselect( 'SELECT f.triggerid,f.functionid,h.hostid,h.host,h.name'. ' FROM functions f'. ' JOIN items i ON f.itemid=i.itemid'. ' JOIN hosts h ON i.hostid=h.hostid'. ' WHERE '.dbConditionInt('f.functionid', array_keys($macros)) ); $host_macros = ['HOST.ID' => 'hostid', 'HOSTNAME' => 'host', 'HOST.HOST' => 'host', 'HOST.NAME' => 'name']; while ($row = DBfetch($result)) { foreach ($macros[$row['functionid']] as $macro => $tokens) { $value = $row[$host_macros[$macro]]; foreach ($tokens as $token) { $macro_values[$row['triggerid']][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Get expression macros like "{?avg(/host/key, 1d)}" or {{?min(/host/key, 1h)}.fmtnum(2)}. * * @param array $macros * @param array $macros[<macro>] * @param string $macros[<macro>]['function'] * @param string $macros[<macro>]['host'] * @param string $macros[<macro>]['key'] * @param string $macros[<macro>]['sec_num'] * @param array $macros[<macro>]['macrofunc'] (optional) * @param string $macros[<macro>]['macrofunc']['function'] * @param array $macros[<macro>]['macrofunc']['parameters'] * @param array $macro_values * * @return array */ protected static function getExpressionMacros(array $macros, array $macro_values) { if (!$macros) { return $macro_values; } $function_data = []; foreach ($macros as $macro => $data) { $macro_data = ['macro' => $macro]; if (array_key_exists('macrofunc', $data)) { $macro_data['macrofunc'] = $data['macrofunc']; } if ($data['function'] === 'last') { $function_data['last'][$data['host']][$data['key']][] = $macro_data; } else { $function_data['other'][$data['host']][$data['key']][$data['function']][$data['sec_num']][] = $macro_data; } } foreach ($function_data as $ftype => $hosts) { foreach ($hosts as $host => $keys) { if ($ftype === 'last') { $db_items = API::Item()->get([ 'output' => ['key_', 'value_type', 'units', 'lastvalue', 'lastclock'], 'selectValueMap' => ['mappings'], 'webitems' => true, 'filter' => [ 'host' => $host, 'key_' => array_keys($keys) ] ]); foreach ($db_items as $db_item) { foreach ($keys[$db_item['key_']] as $macro_data) { if ($db_item['lastclock'] && $db_item['value_type'] != ITEM_VALUE_TYPE_BINARY) { $macro_values[$macro_data['macro']] = array_key_exists('macrofunc', $macro_data) ? CMacroFunction::calcMacrofunc($db_item['lastvalue'], $macro_data['macrofunc']) : formatHistoryValue($db_item['lastvalue'], $db_item); } else { $macro_values[$macro_data['macro']] = UNRESOLVED_MACRO_STRING; } } } } else { $db_items = API::Item()->get([ 'output' => ['itemid', 'key_', 'value_type', 'units'], 'webitems' => true, 'filter' => [ 'host' => $host, 'key_' => array_keys($keys) ] ]); foreach ($db_items as $db_item) { foreach ($keys[$db_item['key_']] as $function => $sec_nums) { foreach ($sec_nums as $sec_num => $_macros) { $value = getItemFunctionalValue($db_item, $function, $sec_num); foreach ($_macros as $macro_data) { if ($value !== null) { $macro_values[$macro_data['macro']] = array_key_exists('macrofunc', $macro_data) ? CMacroFunction::calcMacrofunc($value, $macro_data['macrofunc']) : convertUnits(['value' => $value, 'units' => $db_item['units']]); } else { $macro_values[$macro_data['macro']] = UNRESOLVED_MACRO_STRING; } } } } } } } } return $macro_values; } /** * Get map macros. * * @param array $macros * @param array $macros[<sysmapid>] * @param array $macros[<sysmapid>][<macro>] * @param array $macro_values * @param array $macro_values[<key>] * @param string $macro_values[<key>][<token>] * * @return array */ protected static function getMapMacros(array $macros, array $macro_values): array { if (!$macros) { return $macro_values; } $sysmap_macros = ['MAP.ID' => 'sysmapid', 'MAP.NAME' => 'name']; $db_maps = API::Map()->get([ 'output' => ['sysmapid', 'name'], 'sysmapids' => array_keys($macros), 'preservekeys' => true ]); foreach ($db_maps as $sysmapid => $db_map) { foreach ($macros[$sysmapid] as $macro => $tokens) { $value = $db_map[$sysmap_macros[$macro]]; foreach ($tokens as $token) { $macro_values[$token['key']][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /* * Resolve aggregated macros like {TRIGGER.EVENTS.*}, {TRIGGER(S).PROBLEM.*} and {TRIGGERS.(UN)ACK}. * * @param array $selement * @param string $macro * * @return int */ private static function getTriggersMacroValue(array $selement, string $macro) { switch ($macro) { case 'TRIGGER.EVENTS.ACK': return get_events_unacknowledged($selement, null, null, true); case 'TRIGGER.EVENTS.PROBLEM.ACK': return get_events_unacknowledged($selement, null, TRIGGER_VALUE_TRUE, true); case 'TRIGGER.EVENTS.PROBLEM.UNACK': return get_events_unacknowledged($selement, null, TRIGGER_VALUE_TRUE); case 'TRIGGER.EVENTS.UNACK': return get_events_unacknowledged($selement); case 'TRIGGER.PROBLEM.EVENTS.PROBLEM.ACK': return get_events_unacknowledged($selement, TRIGGER_VALUE_TRUE, TRIGGER_VALUE_TRUE, true); case 'TRIGGER.PROBLEM.EVENTS.PROBLEM.UNACK': return get_events_unacknowledged($selement, TRIGGER_VALUE_TRUE, TRIGGER_VALUE_TRUE); case 'TRIGGERS.UNACK': return get_triggers_unacknowledged($selement); case 'TRIGGERS.PROBLEM.UNACK': return get_triggers_unacknowledged($selement, true); case 'TRIGGERS.ACK': return get_triggers_unacknowledged($selement, null, true); case 'TRIGGERS.PROBLEM.ACK': return get_triggers_unacknowledged($selement, true, true); } } /** * Get aggregated trigger macros. * * @param array $macros * @param array $macros[<key>] * @param array $macros[<key>][<macro>] * @param array $macro_values * @param array $macro_values[<key>] * @param string $macro_values[<key>][<token>] * @param array $selements * @param array $selements[<key>] * @param int $selements[<key>]['elementtype'] * @param array $selements[<key>]['elements'] * * @return array */ protected static function getAggrTriggerMacros(array $macros, array $macro_values, array $selements): array { foreach ($macros as $key => $macro_tokens) { foreach ($macro_tokens as $macro => $tokens) { $value = self::getTriggersMacroValue($selements[$key], $macro); foreach ($tokens as $token) { $macro_values[$key][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Get host macros. * * @param array $macros * @param array $macros[<hostid>] * @param array $macros[<hostid>][<macro>] * @param array $macro_values * @param array $macro_values[<hostid>] * @param string $macro_values[<hostid>][<token>] * * @return array */ protected static function getHostMacrosByHostId(array $macros, array $macro_values): array { if (!$macros) { return $macro_values; } $db_hosts = API::Host()->get([ 'output' => ['host', 'name', 'description'], 'hostids' => array_keys($macros), 'preservekeys' => true ]); $host_macros = ['HOST.ID' => 'hostid', 'HOSTNAME' => 'host', 'HOST.HOST' => 'host', 'HOST.NAME' => 'name', 'HOST.DESCRIPTION' => 'description' ]; foreach ($db_hosts as $hostid => $db_host) { foreach ($macros[$hostid] as $macro => $tokens) { $value = $db_host[$host_macros[$macro]]; foreach ($tokens as $token) { $key = array_key_exists('key', $token) ? $token['key'] : $hostid; $macro_values[$key][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Get host macros by itemid. * * @param array $macros * @param array $macros[<itemid>] * @param array $macros[<itemid>][<macro>] an array of the tokens * @param array $macro_values * @param array $macro_values[<itemid>] * @param string $macro_values[<itemid>][<token>] * * @return array */ protected static function getHostMacrosByItemId(array $macros, array $macro_values) { if (!$macros) { return $macro_values; } $db_items = API::Item()->get([ 'output' => [], 'selectHosts' => ['hostid', 'host', 'name', 'description'], 'itemids' => array_keys($macros), 'webitems' => true, 'preservekeys' => true ]); $host_macros = ['HOST.ID' => 'hostid', 'HOSTNAME' => 'host', 'HOST.HOST' => 'host', 'HOST.NAME' => 'name', 'HOST.DESCRIPTION' => 'description']; foreach ($db_items as $itemid => $db_item) { foreach ($macros[$itemid] as $macro => $tokens) { $value = $db_item['hosts'][0][$host_macros[$macro]]; foreach ($tokens as $token) { $macro_values[$itemid][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Get interface macros by itemid. * * @param array $macros * @param array $macros[<itemid>] * @param array $macros[<itemid>][<macro>] * @param array $macro_values * @param array $macro_values[<itemid>] * @param string $macro_values[<itemid>][<token>] * * @return array */ protected static function getInterfaceMacrosByItemId(array $macros, array $macro_values): array { if (!$macros) { return $macro_values; } $db_items = API::Item()->get([ 'output' => ['hostid', 'interfaceid'], 'itemids' => array_keys($macros), 'webitems' => true, 'preservekeys' => true ]); $interfaceids = []; $hostids = []; foreach ($db_items as $itemid => $db_item) { if ($db_item['interfaceid'] != 0) { // Collecting interface IDs for items with specific interface. $interfaceids[$db_item['interfaceid']][] = $itemid; } else { /* * Collecting host IDs for items without interface. Macros for such items will resolve to either the * Zabbix agent, SNMP, JMX or IPMI interface of the host in this order of priority or to 'UNKNOWN' if * the host does not have any interface. */ $hostids[$db_item['hostid']][] = $itemid; } } $db_interfaces = []; if ($hostids) { $host_interfaces = []; $db_interfaces = API::HostInterface()->get([ 'output' => ['hostid', 'type', 'main', 'useip', 'ip', 'dns', 'port'], 'hostids' => array_keys($hostids), 'filter' => ['main' => INTERFACE_PRIMARY], 'preservekeys' => true ]); usort($db_interfaces, function ($a, $b) { return self::interfacePriorities[$b['type']] <=> self::interfacePriorities[$a['type']]; }); /* * Collecting host interfaces: * - with highest priority for each host * - with interface IDs contained in the $interfaceids array */ foreach ($db_interfaces as $interfaceid => $db_interface) { if (array_key_exists($db_interface['hostid'], $hostids)) { $host_interfaces[$db_interface['hostid']] = $interfaceid; unset($hostids[$db_interface['hostid']]); } elseif (array_key_exists($interfaceid, $interfaceids)) { unset($interfaceids[$interfaceid]); } else { unset($db_interfaces[$interfaceid]); } } } if ($interfaceids) { $db_interfaces += API::HostInterface()->get([ 'output' => ['hostid', 'type', 'main', 'useip', 'ip', 'dns', 'port'], 'interfaceids' => array_keys($interfaceids), 'preservekeys' => true ]); } $db_interfaces = CMacrosResolverHelper::resolveHostInterfaces($db_interfaces); foreach ($db_interfaces as &$db_interface) { $db_interface['conn'] = $db_interface['useip'] == INTERFACE_USE_IP ? $db_interface['ip'] : $db_interface['dns']; } unset($host_interface); $interface_macros = ['IPADDRESS' => 'ip', 'HOST.IP' => 'ip', 'HOST.DNS' => 'dns', 'HOST.CONN' => 'conn', 'HOST.PORT' => 'port' ]; foreach ($db_items as $itemid => $db_item) { if ($db_item['interfaceid'] != 0) { $interfaceid = $db_item['interfaceid']; } elseif (array_key_exists($db_item['hostid'], $host_interfaces)) { $interfaceid = $host_interfaces[$db_item['hostid']]; } else { continue; } if (!array_key_exists($interfaceid, $db_interfaces)) { continue; } foreach ($macros[$itemid] as $macro => $tokens) { $value = $db_interfaces[$interfaceid][$interface_macros[$macro]]; foreach ($tokens as $token) { $macro_values[$itemid][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Resolve interface macros to the main agent interface. * * @param array $macros * @param array $macros[<hostid>] * @param array $macros[<hostid>][<macro>] * @param array $macro_values * @param array $macro_values[<hostid>] * @param string $macro_values[<hostid>][<token>] * * @return array */ protected static function getMainAgentInterfaceMacrosByHostId(array $macros, array $macro_values): array { if (!$macros) { return $macro_values; } $db_interfaces = array_column(API::HostInterface()->get([ 'output' => ['hostid', 'useip', 'ip', 'dns'], 'hostids' => array_keys($macros), 'filter' => [ 'type' => INTERFACE_TYPE_AGENT, 'main' => INTERFACE_PRIMARY ] ]), null, 'hostid'); $data = []; foreach ($db_interfaces as $hostid => $db_interface) { $data[$hostid] = ['ip' => $db_interface['ip'], 'dns' => $db_interface['dns']]; } $data = CMacrosResolver::resolve([ 'config' => 'hostInterfaceIpDnsAgentPrimary', 'data' => $data ]); foreach ($db_interfaces as $hostid => &$db_interface) { $db_interface['ip'] = $data[$hostid]['ip']; $db_interface['dns'] = $data[$hostid]['dns']; $db_interface['conn'] = ($db_interface['useip'] == INTERFACE_USE_IP) ? $db_interface['ip'] : $db_interface['dns']; } unset($db_interface); $interface_macros = ['IPADDRESS' => 'ip', 'HOST.IP' => 'ip', 'HOST.DNS' => 'dns', 'HOST.CONN' => 'conn']; foreach ($db_interfaces as $hostid => $db_interface) { foreach ($macros[$hostid] as $macro => $tokens) { $value = $db_interface[$interface_macros[$macro]]; foreach ($tokens as $token) { $macro_values[$hostid][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Resolve interface macros by interface priority. * * @param array $macros * @param array $macros[<hostid>] * @param array $macros[<hostid>][<macro>] * @param array $macro_values * @param array $macro_values[<hostid>] * @param string $macro_values[<hostid>][<token>] * * @return array */ protected static function getInterfaceMacrosByHostId(array $macros, array $macro_values): array { if (!$macros) { return $macro_values; } $db_interfaces = API::HostInterface()->get([ 'output' => ['hostid', 'type', 'main', 'useip', 'ip', 'dns', 'port'], 'hostids' => array_keys($macros), 'filter' => [ 'main' => INTERFACE_PRIMARY ] ]); usort($db_interfaces, function ($a, $b) { return self::interfacePriorities[$b['type']] <=> self::interfacePriorities[$a['type']]; }); $db_interfaces = CMacrosResolverHelper::resolveHostInterfaces($db_interfaces); $host_interfaces = []; foreach ($db_interfaces as $db_interface) { if (!array_key_exists($db_interface['hostid'], $host_interfaces)) { $host_interfaces[$db_interface['hostid']] = $db_interface; } } foreach ($host_interfaces as &$host_interface) { $host_interface['conn'] = ($host_interface['useip'] == INTERFACE_USE_IP) ? $host_interface['ip'] : $host_interface['dns']; } unset($host_interface); $interface_macros = ['IPADDRESS' => 'ip', 'HOST.IP' => 'ip', 'HOST.DNS' => 'dns', 'HOST.CONN' => 'conn', 'HOST.PORT' => 'port' ]; foreach ($host_interfaces as $hostid => $host_interface) { foreach ($macros[$hostid] as $macro => $tokens) { $value = $host_interface[$interface_macros[$macro]]; foreach ($tokens as $token) { $key = array_key_exists('key', $token) ? $token['key'] : $hostid; $macro_values[$key][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Function returns array holding of inventory macros as a keys and corresponding database fields as value. * * @return array */ protected static function getSupportedHostInventoryMacrosMap(): array { return [ '{INVENTORY.ALIAS}' => 'alias', '{INVENTORY.ASSET.TAG}' => 'asset_tag', '{INVENTORY.CHASSIS}' => 'chassis', '{INVENTORY.CONTACT}' => 'contact', '{PROFILE.CONTACT}' => 'contact', // deprecated '{INVENTORY.CONTRACT.NUMBER}' => 'contract_number', '{INVENTORY.DEPLOYMENT.STATUS}' => 'deployment_status', '{INVENTORY.HARDWARE}' => 'hardware', '{PROFILE.HARDWARE}' => 'hardware', // deprecated '{INVENTORY.HARDWARE.FULL}' => 'hardware_full', '{INVENTORY.HOST.NETMASK}' => 'host_netmask', '{INVENTORY.HOST.NETWORKS}' => 'host_networks', '{INVENTORY.HOST.ROUTER}' => 'host_router', '{INVENTORY.HW.ARCH}' => 'hw_arch', '{INVENTORY.HW.DATE.DECOMM}' => 'date_hw_decomm', '{INVENTORY.HW.DATE.EXPIRY}' => 'date_hw_expiry', '{INVENTORY.HW.DATE.INSTALL}' => 'date_hw_install', '{INVENTORY.HW.DATE.PURCHASE}' => 'date_hw_purchase', '{INVENTORY.INSTALLER.NAME}' => 'installer_name', '{INVENTORY.LOCATION}' => 'location', '{PROFILE.LOCATION}' => 'location', // deprecated '{INVENTORY.LOCATION.LAT}' => 'location_lat', '{INVENTORY.LOCATION.LON}' => 'location_lon', '{INVENTORY.MACADDRESS.A}' => 'macaddress_a', '{PROFILE.MACADDRESS}' => 'macaddress_a', // deprecated '{INVENTORY.MACADDRESS.B}' => 'macaddress_b', '{INVENTORY.MODEL}' => 'model', '{INVENTORY.NAME}' => 'name', '{PROFILE.NAME}' => 'name', // deprecated '{INVENTORY.NOTES}' => 'notes', '{PROFILE.NOTES}' => 'notes', // deprecated '{INVENTORY.OOB.IP}' => 'oob_ip', '{INVENTORY.OOB.NETMASK}' => 'oob_netmask', '{INVENTORY.OOB.ROUTER}' => 'oob_router', '{INVENTORY.OS}' => 'os', '{PROFILE.OS}' => 'os', // deprecated '{INVENTORY.OS.FULL}' => 'os_full', '{INVENTORY.OS.SHORT}' => 'os_short', '{INVENTORY.POC.PRIMARY.CELL}' => 'poc_1_cell', '{INVENTORY.POC.PRIMARY.EMAIL}' => 'poc_1_email', '{INVENTORY.POC.PRIMARY.NAME}' => 'poc_1_name', '{INVENTORY.POC.PRIMARY.NOTES}' => 'poc_1_notes', '{INVENTORY.POC.PRIMARY.PHONE.A}' => 'poc_1_phone_a', '{INVENTORY.POC.PRIMARY.PHONE.B}' => 'poc_1_phone_b', '{INVENTORY.POC.PRIMARY.SCREEN}' => 'poc_1_screen', '{INVENTORY.POC.SECONDARY.CELL}' => 'poc_2_cell', '{INVENTORY.POC.SECONDARY.EMAIL}' => 'poc_2_email', '{INVENTORY.POC.SECONDARY.NAME}' => 'poc_2_name', '{INVENTORY.POC.SECONDARY.NOTES}' => 'poc_2_notes', '{INVENTORY.POC.SECONDARY.PHONE.A}' => 'poc_2_phone_a', '{INVENTORY.POC.SECONDARY.PHONE.B}' => 'poc_2_phone_b', '{INVENTORY.POC.SECONDARY.SCREEN}' => 'poc_2_screen', '{INVENTORY.SERIALNO.A}' => 'serialno_a', '{PROFILE.SERIALNO}' => 'serialno_a', // deprecated '{INVENTORY.SERIALNO.B}' => 'serialno_b', '{INVENTORY.SITE.ADDRESS.A}' => 'site_address_a', '{INVENTORY.SITE.ADDRESS.B}' => 'site_address_b', '{INVENTORY.SITE.ADDRESS.C}' => 'site_address_c', '{INVENTORY.SITE.CITY}' => 'site_city', '{INVENTORY.SITE.COUNTRY}' => 'site_country', '{INVENTORY.SITE.NOTES}' => 'site_notes', '{INVENTORY.SITE.RACK}' => 'site_rack', '{INVENTORY.SITE.STATE}' => 'site_state', '{INVENTORY.SITE.ZIP}' => 'site_zip', '{INVENTORY.SOFTWARE}' => 'software', '{PROFILE.SOFTWARE}' => 'software', // deprecated '{INVENTORY.SOFTWARE.APP.A}' => 'software_app_a', '{INVENTORY.SOFTWARE.APP.B}' => 'software_app_b', '{INVENTORY.SOFTWARE.APP.C}' => 'software_app_c', '{INVENTORY.SOFTWARE.APP.D}' => 'software_app_d', '{INVENTORY.SOFTWARE.APP.E}' => 'software_app_e', '{INVENTORY.SOFTWARE.FULL}' => 'software_full', '{INVENTORY.TAG}' => 'tag', '{PROFILE.TAG}' => 'tag', // deprecated '{INVENTORY.TYPE}' => 'type', '{PROFILE.DEVICETYPE}' => 'type', // deprecated '{INVENTORY.TYPE.FULL}' => 'type_full', '{INVENTORY.URL.A}' => 'url_a', '{INVENTORY.URL.B}' => 'url_b', '{INVENTORY.URL.C}' => 'url_c', '{INVENTORY.VENDOR}' => 'vendor' ]; } /** * Get inventory macros. * * @param array $macros * @param array $macros[<hostid>] * @param array $macros[<hostid>][<macro>] * @param array $macro_values * @param array $macro_values[<key>] * @param string $macro_values[<key>][<token>] * * @return array */ protected static function getInventoryMacrosByHostId(array $macros, array $macro_values): array { if (!$macros) { return $macro_values; } $inventory_macros = self::getSupportedHostInventoryMacrosMap(); $db_hosts = API::Host()->get([ 'output' => ['inventory_mode'], 'selectInventory' => array_values($inventory_macros), 'hostids' => array_keys($macros), 'preservekeys' => true ]); foreach ($db_hosts as $hostid => $db_host) { if ($db_host['inventory_mode'] == HOST_INVENTORY_DISABLED) { continue; } foreach ($macros[$hostid] as $macro => $tokens) { $value = $db_host['inventory'][$inventory_macros['{'.$macro.'}']]; foreach ($tokens as $token) { $key = array_key_exists('key', $token) ? $token['key'] : $hostid; $macro_values[$key][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Get a list of hosts for each selected trigger, in the order in which they located in the expression. * Returns an array of host IDs by trigger ID. * * @param array $triggerids * @param bool $get_host_name Returns host names instead of host IDs. * * @return array */ protected static function getExpressionHosts(array $triggerids, bool $get_host_name = false): array { if (!$triggerids) { return []; } $db_triggers = API::Trigger()->get([ 'output' => ['expression'], 'selectFunctions' => ['functionid', 'itemid'], 'selectItems' => ['itemid', 'hostid'], 'selectHosts' => $get_host_name ? ['hostid', 'host'] : null, 'triggerids' => $triggerids, 'preservekeys' => true ]); $trigger_hosts_by_f_num = []; $expression_parser = new CExpressionParser(['usermacros' => true, 'collapsed_expression' => true]); foreach ($db_triggers as $triggerid => $db_trigger) { if ($expression_parser->parse($db_trigger['expression']) != CParser::PARSE_SUCCESS) { continue; } $db_trigger['functions'] = array_column($db_trigger['functions'], 'itemid', 'functionid'); $db_trigger['items'] = array_column($db_trigger['items'], 'hostid', 'itemid'); if ($get_host_name) { $db_trigger['hosts'] = array_column($db_trigger['hosts'], 'host', 'hostid'); } $tokens = $expression_parser ->getResult() ->getTokensOfTypes([CExpressionParserResult::TOKEN_TYPE_FUNCTIONID_MACRO]); foreach ($tokens as $f_num => $token) { $functionid = substr($token['match'], 1, -1); // strip curly braces $itemid = $db_trigger['functions'][$functionid]; $hostid = $db_trigger['items'][$itemid]; $value = $get_host_name ? $db_trigger['hosts'][$hostid] : $hostid; // Add host reference for macro without numeric index. if ($f_num == 0) { $trigger_hosts_by_f_num[$triggerid][0] = $value; } $trigger_hosts_by_f_num[$triggerid][$f_num + 1] = $value; } } return $trigger_hosts_by_f_num; } /** * Get host macros with references. * * @param array $macros * @param array $macros[<triggerid>] * @param array $macros[<triggerid>][<macro>] * @param array $macros[<triggerid>][<macro>][<f_num>] * @param string $macros[<triggerid>][<macro>][<f_num>][]['token'] * @param string $macros[<triggerid>][<macro>][<f_num>][]['macrofunc'] (optional) * @param string $macros[<triggerid>][<macro>][<f_num>][]'key'] * @param array $macro_values * @param array $macro_values[<key>] * @param string $macro_values[<key>][<token>] * @param array $trigger_hosts_by_f_num * @param array $trigger_hosts_by_f_num[<triggerid>] An array of host IDs. * * @return array */ protected static function getHostNMacros(array $macros, array $macro_values, array $trigger_hosts_by_f_num): array { if (!$macros) { return $macro_values; } $hostids = []; foreach (array_intersect_key($trigger_hosts_by_f_num, $macros) as $triggerid => $_hostids) { $hostids += array_flip($_hostids); } $db_hosts = API::Host()->get([ 'output' => ['hostid', 'host', 'name', 'description'], 'hostids' => array_keys($hostids), 'preservekeys' => true ]); $host_macros = ['HOST.ID' => 'hostid', 'HOSTNAME' => 'host', 'HOST.HOST' => 'host', 'HOST.NAME' => 'name', 'HOST.DESCRIPTION' => 'description' ]; foreach ($macros as $triggerid => $macro_data) { if (!array_key_exists($triggerid, $trigger_hosts_by_f_num)) { continue; } foreach ($macro_data as $macro => $f_num_data) { foreach ($f_num_data as $f_num => $tokens) { if (!array_key_exists($f_num, $trigger_hosts_by_f_num[$triggerid])) { continue; } $hostid = $trigger_hosts_by_f_num[$triggerid][$f_num]; if (array_key_exists($hostid, $db_hosts)) { $value = $db_hosts[$hostid][$host_macros[$macro]]; foreach ($tokens as $token) { $macro_values[$token['key']][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } unset($value); } } return $macro_values; } /** * Get interface macros with references. * * @param array $macros * @param array $macros[<triggerid>] * @param array $macros[<triggerid>][<macro>] * @param array $macros[<triggerid>][<macro>][<f_num>] * @param string $macros[<triggerid>][<macro>][<f_num>][]['token'] * @param string $macros[<triggerid>][<macro>][<f_num>][]['macrofunc'] (optional) * @param string $macros[<triggerid>][<macro>][<f_num>][]'key'] * @param array $macro_values * @param array $macro_values[<key>] * @param array $macro_values[<key>][<macro>] * @param array $trigger_hosts_by_f_num * @param array $trigger_hosts_by_f_num[<triggerid>] An array of host IDs. * * @return array */ protected static function getInterfaceNMacros(array $macros, array $macro_values, array $trigger_hosts_by_f_num): array { if (!$macros) { return $macro_values; } $hostids = []; foreach (array_intersect_key($trigger_hosts_by_f_num, $macros) as $triggerid => $_hostids) { $hostids += array_flip($_hostids); } $db_interfaces = API::HostInterface()->get([ 'output' => ['hostid', 'type', 'useip', 'ip', 'dns'], 'hostids' => array_keys($hostids), 'filter' => ['main' => INTERFACE_PRIMARY] ]); usort($db_interfaces, function ($a, $b) { return self::interfacePriorities[$b['type']] <=> self::interfacePriorities[$a['type']]; }); $host_interfaces = []; foreach ($db_interfaces as $db_interface) { if (!array_key_exists($db_interface['hostid'], $host_interfaces)) { $host_interfaces[$db_interface['hostid']] = $db_interface; } } foreach ($host_interfaces as &$host_interface) { $host_interface['conn'] = ($host_interface['useip'] == INTERFACE_USE_IP) ? $host_interface['ip'] : $host_interface['dns']; } unset($host_interface); $interface_macros = ['IPADDRESS' => 'ip', 'HOST.IP' => 'ip', 'HOST.DNS' => 'dns', 'HOST.CONN' => 'conn']; foreach ($macros as $triggerid => $macro_data) { if (!array_key_exists($triggerid, $trigger_hosts_by_f_num)) { continue; } foreach ($macro_data as $macro => $f_num_data) { foreach ($f_num_data as $f_num => $tokens) { if (!array_key_exists($f_num, $trigger_hosts_by_f_num[$triggerid])) { continue; } $hostid = $trigger_hosts_by_f_num[$triggerid][$f_num]; if (array_key_exists($hostid, $host_interfaces)) { $value = $host_interfaces[$hostid][$interface_macros[$macro]]; foreach ($tokens as $token) { $macro_values[$token['key']][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } unset($value); } } return $macro_values; } /** * Get inventory macros with references. * * @param array $macros * @param array $macros[<triggerid>] * @param array $macros[<triggerid>][<macro>] * @param array $macros[<triggerid>][<macro>][<f_num>] * @param string $macros[<triggerid>][<macro>][<f_num>][]['token'] * @param string $macros[<triggerid>][<macro>][<f_num>][]['macrofunc'] (optional) * @param string $macros[<triggerid>][<macro>][<f_num>][]'key'] * @param array $macro_values * @param array $macro_values[<key>] * @param array $macro_values[<key>][<macro>] * @param array $trigger_hosts_by_f_num * @param array $trigger_hosts_by_f_num[<triggerid>] An array of host IDs. * * @return array */ protected static function getInventoryNMacros(array $macros, array $macro_values, array $trigger_hosts_by_f_num): array { if (!$macros) { return $macro_values; } $hostids = []; foreach (array_intersect_key($trigger_hosts_by_f_num, $macros) as $triggerid => $_hostids) { $hostids += array_flip($_hostids); } $inventory_macros = self::getSupportedHostInventoryMacrosMap(); $db_hosts = API::Host()->get([ 'output' => ['inventory_mode'], 'selectInventory' => array_values($inventory_macros), 'hostids' => array_keys($hostids), 'preservekeys' => true ]); foreach ($macros as $triggerid => $macro_data) { if (!array_key_exists($triggerid, $trigger_hosts_by_f_num)) { continue; } foreach ($macro_data as $macro => $f_num_data) { foreach ($f_num_data as $f_num => $tokens) { if (!array_key_exists($f_num, $trigger_hosts_by_f_num[$triggerid])) { continue; } $hostid = $trigger_hosts_by_f_num[$triggerid][$f_num]; if (array_key_exists($hostid, $db_hosts) && $db_hosts[$hostid]['inventory_mode'] != HOST_INVENTORY_DISABLED) { $value = $db_hosts[$hostid]['inventory'][$inventory_macros['{'.$macro.'}']]; foreach ($tokens as $token) { $macro_values[$token['key']][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } unset($value); } } return $macro_values; } /** * Get expression macros with and without {HOST.HOST<1-9>} references. * * @param array $expr_macros_host_n * @param array $expr_macros_host_n[<triggerid>] * @param array $expr_macros_host_n[<triggerid>][<key>] * @param array $expr_macros_host_n[<triggerid>][<key>][<macro>] * @param string $expr_macros_host_n[<triggerid>][<key>][<macro>]['host'] * @param array $expr_macros_host * @param array $expr_macros_host[<hostid>] * @param array $expr_macros_host[<hostid>][<key>] * @param array $expr_macros_host[<hostid>][<key>][<macro>] * @param string $expr_macros_host[<hostid>][<key>][<macro>]['host'] * @param array $expr_macros * @param array $expr_macros[<macro>] * @param string $expr_macros[<macro>]['host'] * @param array $expr_macros[<macro>]['links'] * @param array $expr_macros[<macro>]['links'][<macro>] An array of keys. * @param array $macro_values * @param array $macro_values[<key>] * @param array $macro_values[<key>][<macro>] * @param array $trigger_hosts_by_f_num * @param array $trigger_hosts_by_f_num[<triggerid>] An array of host IDs. * * @return array */ protected static function getExpressionNMacros(array $expr_macros_host_n, array $expr_macros_host, array $expr_macros, array $macro_values): array { if (!$expr_macros_host_n && !$expr_macros_host && !$expr_macros) { return $macro_values; } $trigger_hosts_by_f_num = self::getExpressionHosts(array_keys($expr_macros_host_n), true); $macro_parser = new CMacroParser(['macros' => ['{HOST.HOST}'], 'ref_type' => CMacroParser::REFERENCE_NUMERIC]); foreach ($expr_macros_host_n as $triggerid => $keys) { if (!array_key_exists($triggerid, $trigger_hosts_by_f_num)) { continue; } foreach ($keys as $key => $_macros) { foreach ($_macros as $_macro => $data) { if ($data['host'] === '') { $reference = 0; $pattern = '#//#'; } else { $macro_parser->parse($data['host']); $reference = $macro_parser->getReference(); $pattern = '#/\{HOST\.HOST[1-9]?\}/#'; } if (!array_key_exists($reference, $trigger_hosts_by_f_num[$triggerid])) { continue; } $host = $trigger_hosts_by_f_num[$triggerid][$reference]; // Replace {HOST.HOST<1-9>} macro with real host name. $macro = preg_replace($pattern, '/'.$host.'/', $_macro, 1); if (!array_key_exists($macro, $expr_macros)) { $expr_macros[$macro] = ['host' => $host] + $data; } $expr_macros[$macro]['links'][$_macro][] = $key; } } } $db_hosts = $expr_macros_host ? API::Host()->get([ 'output' => ['host'], 'hostids' => array_keys($expr_macros_host), 'preservekeys' => true ]) : []; foreach ($expr_macros_host as $hostid => $keys) { if (!array_key_exists($hostid, $db_hosts)) { continue; } foreach ($keys as $key => $_macros) { foreach ($_macros as $_macro => $data) { // Replace {HOST.HOST} macro with real host name. $pattern = $data['host'] === '' ? '#//#' : '#/\{HOST\.HOST\}/#'; $macro = preg_replace($pattern, '/'.$db_hosts[$hostid]['host'].'/', $_macro, 1); if (!array_key_exists($macro, $expr_macros)) { $expr_macros[$macro] = ['host' => $db_hosts[$hostid]['host']] + $data; } $expr_macros[$macro]['links'][$_macro][] = $key; } } } $expr_macro_values = self::getExpressionMacros($expr_macros, []); foreach ($expr_macros as $macro => $expr_macro) { if (!array_key_exists($macro, $expr_macro_values)) { continue; } foreach ($expr_macro['links'] as $_macro => $keys) { foreach ($keys as $key) { $macro_values[$key][$_macro] = $expr_macro_values[$macro]; } } } return $macro_values; } /** * Get macros with values. * * @param array $usermacros * @param array $usermacros[n]['hostids'] The list of host ids; [<hostid1>, ...]. * @param array $usermacros[n]['macros'] The list of user macros to resolve. * @param array $usermacros[n]['macros'][<token>] * @param string $usermacros[n]['macros'][<token>]['macro'] * @param string $usermacros[n]['macros'][<token>]['context'] * @param array $usermacros[n]['macros'][<token>]['macrofunc'] (optional) * @param array $macro_values * @param array $macro_values[<key>] * @param array $macro_values[<key>][<macro>] * @param bool $unset_undefined Unset undefined macros. * * @return array */ protected static function getUserMacros(array $usermacros, array $macro_values, bool $unset_undefined = false) { if (!$usermacros) { return $macro_values; } // User macros. $hostids = []; foreach ($usermacros as $usermacros_data) { $hostids += array_flip($usermacros_data['hostids']); } $user_macro_parser_with_regex = new CUserMacroParser(['allow_regex' => true]); $user_macro_parser = new CUserMacroParser(); /* * @var array $host_templates * @var array $host_templates[<hostid>] array of templates */ $host_templates = []; /* * @var array $host_macros * @var array $host_macros[<hostid>] * @var array $host_macros[<hostid>][<macro>] macro base without curly braces * @var string $host_macros[<hostid>][<macro>]['value'] base macro value (without context and regex); * can be null * @var array $host_macros[<hostid>][<macro>]['contexts'] context values; ['<context1>' => '<value1>', ...] * @var array $host_macros[<hostid>][<macro>]['regex'] regex values; ['<regex1>' => '<value1>', ...] */ $host_macros = []; if ($hostids) { do { $hostids = array_keys($hostids); $db_host_macros = API::UserMacro()->get([ 'output' => ['macro', 'value', 'type', 'hostid'], 'hostids' => $hostids ]); foreach ($db_host_macros as $db_host_macro) { if ($user_macro_parser_with_regex->parse($db_host_macro['macro']) != CParser::PARSE_SUCCESS) { continue; } $hostid = $db_host_macro['hostid']; $macro = $user_macro_parser_with_regex->getMacro(); $context = $user_macro_parser_with_regex->getContext(); $regex = $user_macro_parser_with_regex->getRegex(); $value = self::getMacroValue($db_host_macro); if (!array_key_exists($hostid, $host_macros) || !array_key_exists($macro, $host_macros[$hostid])) { $host_macros[$hostid][$macro] = ['value' => null, 'contexts' => [], 'regex' => []]; } if ($context === null && $regex === null) { $host_macros[$hostid][$macro]['value'] = $value; } elseif ($regex !== null) { $host_macros[$hostid][$macro]['regex'][$regex] = $value; } else { $host_macros[$hostid][$macro]['contexts'][$context] = $value; } } foreach ($hostids as $hostid) { $host_templates[$hostid] = []; } $templateids = []; $db_host_templates = DBselect( 'SELECT ht.hostid,ht.templateid'. ' FROM hosts_templates ht'. ' WHERE '.dbConditionInt('ht.hostid', $hostids) ); while ($db_host_template = DBfetch($db_host_templates)) { $host_templates[$db_host_template['hostid']][] = $db_host_template['templateid']; $templateids[$db_host_template['templateid']] = true; } // only unprocessed templates will be populated $hostids = []; foreach (array_keys($templateids) as $templateid) { if (!array_key_exists($templateid, $host_templates)) { $hostids[$templateid] = true; } } } while ($hostids); } // Reordering only regex array. $host_macros = self::sortRegexHostMacros($host_macros); $all_macros_resolved = true; foreach ($usermacros as &$usermacros_data) { $hostids = array_unique($usermacros_data['hostids']); natsort($hostids); foreach ($usermacros_data['macros'] as $usermacro => &$data) { $data['value'] = self::getHostUserMacros($hostids, $data['macro'], $data['context'], $host_templates, $host_macros ); if ($data['value']['value'] === null) { $all_macros_resolved = false; } } unset($data); } unset($usermacros_data); if (!$all_macros_resolved) { // Global macros. $db_global_macros = API::UserMacro()->get([ 'output' => ['macro', 'value', 'type'], 'globalmacro' => true ]); /* * @var array $global_macros * @var array $global_macros[<macro>] macro base without curly braces * @var string $global_macros[<macro>]['value'] base macro value (without context and regex); * can be null * @var array $global_macros[<macro>]['contexts'] context values; ['<context1>' => '<value1>', ...] * @var array $global_macros[<macro>]['regex'] regex values; ['<regex1>' => '<value1>', ...] */ $global_macros = []; foreach ($db_global_macros as $db_global_macro) { if ($user_macro_parser_with_regex->parse($db_global_macro['macro']) == CParser::PARSE_SUCCESS) { $macro = $user_macro_parser_with_regex->getMacro(); $context = $user_macro_parser_with_regex->getContext(); $regex = $user_macro_parser_with_regex->getRegex(); $value = self::getMacroValue($db_global_macro); if (!array_key_exists($macro, $global_macros)) { $global_macros[$macro] = ['value' => null, 'contexts' => [], 'regex' => []]; } if ($context === null && $regex === null) { $global_macros[$macro]['value'] = $value; } elseif ($regex !== null) { $global_macros[$macro]['regex'][$regex] = $value; } else { $global_macros[$macro]['contexts'][$context] = $value; } } } // Reordering only regex array. $global_macros = self::sortRegexGlobalMacros($global_macros); foreach ($usermacros as &$usermacros_data) { foreach ($usermacros_data['macros'] as $usermacro => &$data) { if ($data['value']['value'] === null) { if (array_key_exists($data['macro'], $global_macros)) { if ($data['context'] !== null && array_key_exists($data['context'], $global_macros[$data['macro']]['contexts'])) { $data['value']['value'] = $global_macros[$data['macro']]['contexts'][$data['context']]; } elseif ($data['context'] !== null && count($global_macros[$data['macro']]['regex'])) { foreach ($global_macros[$data['macro']]['regex'] as $regex => $val) { if (preg_match('/'.self::handleSlashEscaping($regex).'/', $data['context'])) { $data['value']['value'] = $val; break; } } } if ($data['value']['value'] === null && $global_macros[$data['macro']]['value'] !== null) { if ($data['context'] === null) { $data['value']['value'] = $global_macros[$data['macro']]['value']; } elseif ($data['value']['value_default'] === null) { $data['value']['value_default'] = $global_macros[$data['macro']]['value']; } } } } } unset($data); } unset($usermacros_data); } foreach ($usermacros as $key => $usermacros_data) { foreach ($usermacros_data['macros'] as $usermacro => $data) { if ($data['value']['value'] !== null) { $usermacros[$key]['macros'][$usermacro] = array_key_exists('macrofunc', $data) ? CMacroFunction::calcMacrofunc($data['value']['value'], $data['macrofunc']) : $data['value']['value']; } elseif ($data['value']['value_default'] !== null) { $usermacros[$key]['macros'][$usermacro] = array_key_exists('macrofunc', $data) ? CMacroFunction::calcMacrofunc($data['value']['value_default'], $data['macrofunc']) : $data['value']['value_default']; } // Unresolved macro. elseif ($unset_undefined) { unset($usermacros[$key]['macros'][$usermacro]); } else { $usermacros[$key]['macros'][$usermacro] = $usermacro; } } } foreach ($usermacros as $key => $usermacros_data) { $macro_values[$key] = array_key_exists($key, $macro_values) ? array_merge($macro_values[$key], $usermacros_data['macros']) : $usermacros_data['macros']; } return $macro_values; } /** * Get user macro from the requested hosts. * * Use the base value returned by host macro as default value when expanding expand global macro. This will ensure * the following user macro resolving priority: * 1) host/template context macro * 2) global context macro * 3) host/template base (default) macro * 4) global base (default) macro * * @param array $hostids The sorted list of hosts where macros will be looked for (hostid => hostid) * @param string $macro Macro base without curly braces, for example: SNMP_COMMUNITY * @param string $context Macro context to resolve * @param array $host_templates The list of linked templates (see getUserMacros() for more details) * @param array $host_macros The list of macros on hosts (see getUserMacros() for more details) * @param string $value_default Value * * @return array */ private static function getHostUserMacros(array $hostids, $macro, $context, array $host_templates, array $host_macros, $value_default = null) { foreach ($hostids as $hostid) { if (array_key_exists($hostid, $host_macros) && array_key_exists($macro, $host_macros[$hostid])) { // Searching context coincidence with macro contexts. if ($context !== null && array_key_exists($context, $host_macros[$hostid][$macro]['contexts'])) { return [ 'value' => $host_macros[$hostid][$macro]['contexts'][$context], 'value_default' => $value_default ]; } // Searching context coincidence, if regex array not empty. elseif ($context !== null && count($host_macros[$hostid][$macro]['regex'])) { foreach ($host_macros[$hostid][$macro]['regex'] as $regex => $val) { if (preg_match('/'.self::handleSlashEscaping($regex).'/', $context) === 1) { return [ 'value' => $val, 'value_default' => $value_default ]; } } } if ($host_macros[$hostid][$macro]['value'] !== null) { if ($context === null) { return ['value' => $host_macros[$hostid][$macro]['value'], 'value_default' => $value_default]; } elseif ($value_default === null) { $value_default = $host_macros[$hostid][$macro]['value']; } } } } if (!$host_templates) { return ['value' => null, 'value_default' => $value_default]; } $templateids = []; foreach ($hostids as $hostid) { if (array_key_exists($hostid, $host_templates)) { foreach ($host_templates[$hostid] as $templateid) { $templateids[$templateid] = true; } } } if ($templateids) { $templateids = array_keys($templateids); natsort($templateids); return self::getHostUserMacros($templateids, $macro, $context, $host_templates, $host_macros, $value_default ); } return ['value' => null, 'value_default' => $value_default]; } /** * Get and resolve user data macros like name, surname, username. Input array contains a collection of prepared * and unresolved macros. Get data from API service, because direct requests to API do no have CWebUser data. * * Example input: * array ( * 0 => array ( * '{USER.FULLNAME}' => '*UNKNOWN*', * ), * 1 => array ( * '{USER.NAME}' => '*UNKNOWN*', * '{USER.SURNAME}' => '*UNKNOWN*', * ) * ) * * Output: * array ( * 0 => array ( * '{USER.FULLNAME}' => 'Zabbix Administrator', * ), * 1 => array ( * '{USER.NAME}' => 'Zabbix', * '{USER.SURNAME}' => 'Administrator', * ) * ) * * @param array $macros * @param array $macros[<n>] * @param array $macros[<n>][<macro>] * @param array $macro_values * @param array $macro_values[<n>] * @param string $macro_values[<n>][<token>] * * @return array */ protected static function getUserDataMacros(array $macros, array $macro_values): array { foreach ($macros as $n => $macro_data) { foreach ($macro_data as $macro => $tokens) { switch ($macro) { case 'USER.ALIAS': // Deprecated in version 5.4. case 'USER.USERNAME': $value = CApiService::$userData['username']; break; case 'USER.FULLNAME': $fullname = []; foreach (['name', 'surname'] as $field) { if (CApiService::$userData[$field] !== '') { $fullname[] = CApiService::$userData[$field]; } } $value = $fullname ? implode(' ', array_merge($fullname, ['('.CApiService::$userData['username'].')'])) : CApiService::$userData['username']; break; case 'USER.NAME': $value = CApiService::$userData['name']; break; case 'USER.SURNAME': $value = CApiService::$userData['surname']; break; } foreach ($tokens as $token) { $macro_values[$n][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } return $macro_values; } /** * Escape slashes in the regular expression based on preceding backslashes. * * @param string $regex * * @return string */ private static function handleSlashEscaping(string $regex): string { $formatted_regex = ''; $backslash_count = 0; for ($p = 0; isset($regex[$p]); $p++) { if ($regex[$p] === '\\') { $backslash_count++; } else { if ($regex[$p] === '/' && $backslash_count % 2 == 0) { $formatted_regex .= '\\'; } $backslash_count = 0; } $formatted_regex .= $regex[$p]; } return $formatted_regex; } /** * Get macro value refer by type. * * @param array $macro * * @return string */ public static function getMacroValue(array $macro): string { return ($macro['type'] == ZBX_MACRO_TYPE_SECRET || $macro['type'] == ZBX_MACRO_TYPE_VAULT) ? ZBX_SECRET_MASK : $macro['value']; } /** * Sorting host macros. * * @param array $host_macros * * @return array */ private static function sortRegexHostMacros(array $host_macros): array { foreach ($host_macros as &$macros) { foreach ($macros as &$value) { $value['regex'] = self::sortRegex($value['regex']); } unset($value); } unset($macros); return $host_macros; } /** * Sorting global macros. * * @param array $global_macros * * @return array */ private static function sortRegexGlobalMacros(array $global_macros): array { foreach ($global_macros as &$value) { $value['regex'] = self::sortRegex($value['regex']); } unset($value); return $global_macros; } /** * Sort regex. * * @param array $macros * * @return array */ private static function sortRegex(array $macros): array { $keys = array_keys($macros); usort($keys, 'strcmp'); $new_array = []; foreach($keys as $key) { $new_array[$key] = $macros[$key]; } return $new_array; } /** * Get manualinput macros. * * @param array $macros * @param array $macros[<id>] * @param array $macros[<id>][<macro>] * @param array $macro_values * @param array $macro_values[<id>] * @param string $macro_values[<id>][<token>] * @param array $manualinput_values * @param string $manualinput_values[<id>] * * @return array */ protected static function getManualInputMacros(array $macros, array $macro_values, array $manualinput_values): array { foreach ($macros as $id => $macro_tokens) { if (array_key_exists($id, $manualinput_values)) { $value = $manualinput_values[$id]; foreach ($macro_tokens as $macro => $tokens) { foreach ($tokens as $token) { $macro_values[$id][$token['token']] = array_key_exists('macrofunc', $token) ? CMacroFunction::calcMacrofunc($value, $token['macrofunc']) : $value; } } } } return $macro_values; } }