1<?php 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22class CMacrosResolverGeneral { 23 24 const PATTERN_HOST_INTERNAL = 'HOST\.HOST|HOSTNAME'; 25 const PATTERN_MACRO_PARAM = '[1-9]?'; 26 27 /** 28 * Interface priorities. 29 * 30 * @var array 31 */ 32 protected $interfacePriorities = [ 33 INTERFACE_TYPE_AGENT => 4, 34 INTERFACE_TYPE_SNMP => 3, 35 INTERFACE_TYPE_JMX => 2, 36 INTERFACE_TYPE_IPMI => 1 37 ]; 38 39 /** 40 * Work config name. 41 * 42 * @var string 43 */ 44 protected $config = ''; 45 46 /** 47 * Get reference macros for trigger. 48 * If macro reference non existing value it expands to empty string. 49 * 50 * @param string $expression 51 * @param array $references 52 * 53 * @return array 54 */ 55 protected function resolveTriggerReferences($expression, $references) { 56 $matched_macros = $this->getMacroPositions($expression, ['usermacros' => true]); 57 58 // Replace user macros with string 'macro' to make values search easier. 59 foreach (array_reverse($matched_macros, true) as $pos => $macro) { 60 $expression = substr_replace($expression, 'macro', $pos, strlen($macro)); 61 } 62 63 // Replace functionids with string 'function' to make values search easier. 64 $expression = preg_replace('/\{[0-9]+\}/', 'function', $expression); 65 66 // Replace whitespace with emptyness to make value search easier. 67 $expression = str_replace(" \r\n\t", '', $expression); 68 69 // Search for numeric values in expression. 70 preg_match_all('/((?<![\)\.0-9]|[\.0-9]['.ZBX_BYTE_SUFFIXES.ZBX_TIME_SUFFIXES.']|function)\-?'. 71 '([.][0-9]+|[0-9]+[.]?[0-9]*)['.ZBX_BYTE_SUFFIXES.ZBX_TIME_SUFFIXES.']?)/', $expression, $values); 72 73 $macro_values = []; 74 75 foreach (array_keys($references) as $reference) { 76 $i = (int) $reference[1] - 1; 77 $macro_values[$reference] = array_key_exists($i, $values[0]) ? $values[0][$i] : ''; 78 } 79 80 return $macro_values; 81 } 82 83 /** 84 * Checking existence of the macros. 85 * 86 * @param array $texts 87 * @param array $type 88 * 89 * @return bool 90 */ 91 protected function hasMacros(array $texts, array $types) { 92 foreach ($texts as $text) { 93 if ($this->getMacroPositions($text, $types)) { 94 return true; 95 } 96 } 97 98 return false; 99 } 100 101 /** 102 * Transform types, used in extractMacros() function to types which can be used in getMacroPositions(). 103 * 104 * @param array $types 105 * 106 * @return array 107 */ 108 protected function transformToPositionTypes(array $types) { 109 foreach (['macros', 'macros_n', 'macro_funcs_n'] as $type) { 110 if (array_key_exists($type, $types)) { 111 $patterns = []; 112 foreach ($types[$type] as $key => $_patterns) { 113 $patterns = array_merge($patterns, $_patterns); 114 } 115 $types[$type] = $patterns; 116 } 117 } 118 119 return $types; 120 } 121 122 /** 123 * Extract positions of the macros from a string. 124 * 125 * @param string $text 126 * @param array $types 127 * @param bool $types['usermacros'] 128 * @param array $types['macros'][<macro_patterns>] 129 * @param array $types['macros_n'][<macro_patterns>] 130 * @param array $types['macro_funcs_n'][<macro_patterns>] 131 * @param bool $types['references'] 132 * @param bool $types['lldmacros'] 133 * @param bool $types['functionids'] 134 * @param bool $types['replacements'] 135 * 136 * @return array 137 */ 138 public function getMacroPositions($text, array $types) { 139 $macros = []; 140 $extract_usermacros = array_key_exists('usermacros', $types); 141 $extract_macros = array_key_exists('macros', $types); 142 $extract_macros_n = array_key_exists('macros_n', $types); 143 $extract_macro_funcs_n = array_key_exists('macro_funcs_n', $types); 144 $extract_references = array_key_exists('references', $types); 145 $extract_lldmacros = array_key_exists('lldmacros', $types); 146 $extract_functionids = array_key_exists('functionids', $types); 147 $extract_replacements = array_key_exists('replacements', $types); 148 149 if ($extract_usermacros) { 150 $user_macro_parser = new CUserMacroParser(); 151 } 152 153 if ($extract_macros) { 154 $macro_parser = new CMacroParser($types['macros']); 155 } 156 157 if ($extract_macros_n) { 158 $macro_n_parser = new CMacroParser($types['macros_n'], ['allow_reference' => true]); 159 } 160 161 if ($extract_macro_funcs_n) { 162 $macro_func_n_parser = new CMacroFunctionParser($types['macro_funcs_n'], ['allow_reference' => true]); 163 } 164 165 if ($extract_references) { 166 $reference_parser = new CReferenceParser(); 167 } 168 169 if ($extract_lldmacros) { 170 $lld_macro_parser = new CLLDMacroParser(); 171 $lld_macro_function_parser = new CLLDMacroFunctionParser(); 172 } 173 174 if ($extract_functionids) { 175 $functionid_parser = new CFunctionIdParser(); 176 } 177 178 if ($extract_replacements) { 179 $replacement_parser = new CReplacementParser(); 180 } 181 182 for ($pos = 0; isset($text[$pos]); $pos++) { 183 if ($extract_usermacros && $user_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 184 $macros[$pos] = $user_macro_parser->getMatch(); 185 $pos += $user_macro_parser->getLength() - 1; 186 } 187 elseif ($extract_macros && $macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 188 $macros[$pos] = $macro_parser->getMatch(); 189 $pos += $macro_parser->getLength() - 1; 190 } 191 elseif ($extract_macros_n && $macro_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 192 $macros[$pos] = $macro_n_parser->getMatch(); 193 $pos += $macro_n_parser->getLength() - 1; 194 } 195 elseif ($extract_macro_funcs_n && $macro_func_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 196 $macros[$pos] = $macro_func_n_parser->getMatch(); 197 $pos += $macro_func_n_parser->getLength() - 1; 198 } 199 elseif ($extract_references && $reference_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 200 $macros[$pos] = $reference_parser->getMatch(); 201 $pos += $reference_parser->getLength() - 1; 202 } 203 elseif ($extract_lldmacros && $lld_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 204 $macros[$pos] = $lld_macro_parser->getMatch(); 205 $pos += $lld_macro_parser->getLength() - 1; 206 } 207 elseif ($extract_lldmacros && $lld_macro_function_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 208 $macros[$pos] = $lld_macro_function_parser->getMatch(); 209 $pos += $lld_macro_function_parser->getLength() - 1; 210 } 211 elseif ($extract_functionids && $functionid_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 212 $macros[$pos] = $functionid_parser->getMatch(); 213 $pos += $functionid_parser->getLength() - 1; 214 } 215 elseif ($extract_replacements && $replacement_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 216 $macros[$pos] = $replacement_parser->getMatch(); 217 $pos += $replacement_parser->getLength() - 1; 218 } 219 } 220 221 return $macros; 222 } 223 224 /** 225 * Extract macros from a string. 226 * 227 * @param array $texts 228 * @param array $types 229 * @param bool $types['usermacros'] 230 * @param array $types['macros'][][<macro_patterns>] 231 * @param array $types['macros_n'][][<macro_patterns>] 232 * @param array $types['macro_funcs_n'][][<macro_patterns>] 233 * @param bool $types['references'] 234 * @param bool $types['lldmacros'] 235 * @param bool $types['functionids'] 236 * 237 * @return array 238 */ 239 protected function extractMacros(array $texts, array $types) { 240 $macros = []; 241 $extract_usermacros = array_key_exists('usermacros', $types); 242 $extract_macros = array_key_exists('macros', $types); 243 $extract_macros_n = array_key_exists('macros_n', $types); 244 $extract_macro_funcs_n = array_key_exists('macro_funcs_n', $types); 245 $extract_references = array_key_exists('references', $types); 246 $extract_lldmacros = array_key_exists('lldmacros', $types); 247 $extract_functionids = array_key_exists('functionids', $types); 248 249 if ($extract_usermacros) { 250 $macros['usermacros'] = []; 251 252 $user_macro_parser = new CUserMacroParser(); 253 } 254 255 if ($extract_macros) { 256 $macros['macros'] = []; 257 258 foreach ($types['macros'] as $key => $macro_patterns) { 259 $types['macros'][$key] = new CMacroParser($macro_patterns); 260 $macros['macros'][$key] = []; 261 } 262 } 263 264 if ($extract_macros_n) { 265 $macros['macros_n'] = []; 266 267 foreach ($types['macros_n'] as $key => $macro_patterns) { 268 $types['macros_n'][$key] = new CMacroParser($macro_patterns, ['allow_reference' => true]); 269 $macros['macros_n'][$key] = []; 270 } 271 } 272 273 if ($extract_macro_funcs_n) { 274 $macros['macro_funcs_n'] = []; 275 276 foreach ($types['macro_funcs_n'] as $key => $macro_patterns) { 277 $types['macro_funcs_n'][$key] = new CMacroFunctionParser($macro_patterns, ['allow_reference' => true]); 278 $macros['macro_funcs_n'][$key] = []; 279 } 280 } 281 282 if ($extract_references) { 283 $macros['references'] = []; 284 285 $reference_parser = new CReferenceParser(); 286 } 287 288 if ($extract_lldmacros) { 289 $macros['lldmacros'] = []; 290 291 $lld_macro_parser = new CLLDMacroParser(); 292 $lld_macro_function_parser = new CLLDMacroFunctionParser(); 293 } 294 295 if ($extract_functionids) { 296 $macros['functionids'] = []; 297 298 $functionid_parser = new CFunctionIdParser(); 299 } 300 301 foreach ($texts as $text) { 302 for ($pos = 0; isset($text[$pos]); $pos++) { 303 if ($extract_usermacros && $user_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 304 $macros['usermacros'][$user_macro_parser->getMatch()] = null; 305 $pos += $user_macro_parser->getLength() - 1; 306 continue; 307 } 308 309 if ($extract_macros) { 310 foreach ($types['macros'] as $key => $macro_parser) { 311 if ($macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 312 $macros['macros'][$key][$macro_parser->getMatch()] = true; 313 $pos += $macro_parser->getLength() - 1; 314 continue 2; 315 } 316 } 317 } 318 319 if ($extract_macros_n) { 320 foreach ($types['macros_n'] as $key => $macro_n_parser) { 321 if ($macro_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 322 $macros['macros_n'][$key][$macro_n_parser->getMatch()] = [ 323 'macro' => $macro_n_parser->getMacro(), 324 'f_num' => $macro_n_parser->getN() 325 ]; 326 $pos += $macro_n_parser->getLength() - 1; 327 continue 2; 328 } 329 } 330 } 331 332 if ($extract_macro_funcs_n) { 333 foreach ($types['macro_funcs_n'] as $key => $macro_func_n_parser) { 334 if ($macro_func_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 335 $macro_n_parser = $macro_func_n_parser->getMacroParser(); 336 $function_parser = $macro_func_n_parser->getFunctionParser(); 337 $function_parameters = []; 338 339 foreach ($function_parser->getParamsRaw()['parameters'] as $param_raw) { 340 switch ($param_raw['type']) { 341 case CFunctionParser::PARAM_UNQUOTED: 342 $function_parameters[] = $param_raw['raw']; 343 break; 344 345 case CFunctionParser::PARAM_QUOTED: 346 $function_parameters[] = CFunctionParser::unquoteParam($param_raw['raw']); 347 break; 348 } 349 } 350 351 $macros['macro_funcs_n'][$key][$macro_func_n_parser->getMatch()] = [ 352 'macro' => $macro_n_parser->getMacro(), 353 'f_num' => $macro_n_parser->getN(), 354 'function' => $function_parser->getFunction(), 355 'parameters' => $function_parameters 356 ]; 357 $pos += $macro_func_n_parser->getLength() - 1; 358 continue 2; 359 } 360 } 361 } 362 363 if ($extract_references && $reference_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 364 $macros['references'][$reference_parser->getMatch()] = null; 365 $pos += $reference_parser->getLength() - 1; 366 continue; 367 } 368 369 if ($extract_lldmacros) { 370 if ($lld_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 371 $macros['lldmacros'][$lld_macro_parser->getMatch()] = null; 372 $pos += $lld_macro_parser->getLength() - 1; 373 continue; 374 } 375 elseif ($lld_macro_function_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 376 $macros['lldmacros'][$lld_macro_function_parser->getMatch()] = null; 377 $pos += $lld_macro_function_parser->getLength() - 1; 378 continue; 379 } 380 } 381 382 if ($extract_functionids && $functionid_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 383 $macros['functionids'][$functionid_parser->getMatch()] = null; 384 $pos += $functionid_parser->getLength() - 1; 385 continue; 386 } 387 } 388 } 389 390 if ($extract_macros) { 391 foreach ($types['macros'] as $key => $macro_parser) { 392 $macros['macros'][$key] = array_keys($macros['macros'][$key]); 393 } 394 } 395 396 return $macros; 397 } 398 399 /** 400 * Returns the list of the item key parameters. 401 * 402 * @param string $params_raw 403 * 404 * @return array 405 */ 406 private function getItemKeyParameters($params_raw) { 407 $item_key_parameters = []; 408 409 foreach ($params_raw as $param_raw) { 410 switch ($param_raw['type']) { 411 case CItemKey::PARAM_ARRAY: 412 $item_key_parameters = array_merge($item_key_parameters, 413 $this->getItemKeyParameters($param_raw['parameters']) 414 ); 415 break; 416 417 case CItemKey::PARAM_UNQUOTED: 418 $item_key_parameters[] = $param_raw['raw']; 419 break; 420 421 case CItemKey::PARAM_QUOTED: 422 $item_key_parameters[] = CItemKey::unquoteParam($param_raw['raw']); 423 break; 424 } 425 } 426 427 return $item_key_parameters; 428 } 429 430 /** 431 * Extract macros from an item key. 432 * 433 * @param string $key an item key 434 * @param array $types the types of macros (see extractMacros() for more details) 435 * 436 * @return array see extractMacros() for more details 437 */ 438 protected function extractItemKeyMacros($key, array $types) { 439 $item_key_parser = new CItemKey(); 440 441 $item_key_parameters = []; 442 if ($item_key_parser->parse($key) == CParser::PARSE_SUCCESS) { 443 $item_key_parameters = $this->getItemKeyParameters($item_key_parser->getParamsRaw()); 444 } 445 446 return $this->extractMacros($item_key_parameters, $types); 447 } 448 449 /** 450 * Extract macros from a trigger function. 451 * 452 * @param string $function a trigger function, for example 'last({$LAST})' 453 * @param array $types the types of macros (see extractMacros() for more details) 454 * 455 * @return array see extractMacros() for more details 456 */ 457 protected function extractFunctionMacros($function, array $types) { 458 $function_parser = new CFunctionParser(); 459 460 $function_parameters = []; 461 if ($function_parser->parse($function) == CParser::PARSE_SUCCESS) { 462 foreach ($function_parser->getParamsRaw()['parameters'] as $param_raw) { 463 switch ($param_raw['type']) { 464 case CFunctionParser::PARAM_UNQUOTED: 465 $function_parameters[] = $param_raw['raw']; 466 break; 467 468 case CFunctionParser::PARAM_QUOTED: 469 $function_parameters[] = CFunctionParser::unquoteParam($param_raw['raw']); 470 break; 471 } 472 } 473 } 474 475 return $this->extractMacros($function_parameters, $types); 476 } 477 478 /** 479 * Resolves macros in the item key parameters. 480 * 481 * @param string $key_chain an item key chain 482 * @param string $params_raw 483 * @param array $macros the list of macros (['{<MACRO>}' => '<value>', ...]) 484 * @param array $types the types of macros (see getMacroPositions() for more details) 485 * 486 * @return string 487 */ 488 private function resolveItemKeyParamsMacros($key_chain, array $params_raw, array $macros, array $types) { 489 foreach (array_reverse($params_raw) as $param_raw) { 490 $param = $param_raw['raw']; 491 $forced = false; 492 493 switch ($param_raw['type']) { 494 case CItemKey::PARAM_ARRAY: 495 $param = $this->resolveItemKeyParamsMacros($param, $param_raw['parameters'], $macros, $types); 496 break; 497 498 case CItemKey::PARAM_QUOTED: 499 $param = CItemKey::unquoteParam($param); 500 $forced = true; 501 // break; is not missing here 502 503 case CItemKey::PARAM_UNQUOTED: 504 $matched_macros = $this->getMacroPositions($param, $types); 505 506 foreach (array_reverse($matched_macros, true) as $pos => $macro) { 507 $param = substr_replace($param, $macros[$macro], $pos, strlen($macro)); 508 } 509 510 $param = quoteItemKeyParam($param, $forced); 511 break; 512 } 513 514 $key_chain = substr_replace($key_chain, $param, $param_raw['pos'], strlen($param_raw['raw'])); 515 } 516 517 return $key_chain; 518 } 519 520 /** 521 * Resolves macros in the item key. 522 * 523 * @param string $key an item key 524 * @param array $macros the list of macros (['{<MACRO>}' => '<value>', ...]) 525 * @param array $types the types of macros (see getMacroPositions() for more details) 526 * 527 * @return string 528 */ 529 protected function resolveItemKeyMacros($key, array $macros, array $types) { 530 $item_key_parser = new CItemKey(); 531 532 if ($item_key_parser->parse($key) == CParser::PARSE_SUCCESS) { 533 $key = $this->resolveItemKeyParamsMacros($key, $item_key_parser->getParamsRaw(), $macros, $types); 534 } 535 536 return $key; 537 } 538 539 /** 540 * Resolves macros in the trigger function parameters. 541 * 542 * @param string $function a trigger function 543 * @param array $macros the list of macros (['{<MACRO>}' => '<value>', ...]) 544 * @param array $types the types of macros (see getMacroPositions() for more details) 545 * 546 * @return string 547 */ 548 protected function resolveFunctionMacros($function, array $macros, array $types) { 549 $function_parser = new CFunctionParser(); 550 551 if ($function_parser->parse($function) == CParser::PARSE_SUCCESS) { 552 $params_raw = $function_parser->getParamsRaw(); 553 $function_chain = $params_raw['raw']; 554 555 foreach (array_reverse($params_raw['parameters']) as $param_raw) { 556 $param = $param_raw['raw']; 557 $forced = false; 558 559 switch ($param_raw['type']) { 560 case CFunctionParser::PARAM_QUOTED: 561 $param = CFunctionParser::unquoteParam($param); 562 $forced = true; 563 // break; is not missing here 564 565 case CFunctionParser::PARAM_UNQUOTED: 566 $matched_macros = $this->getMacroPositions($param, $types); 567 568 foreach (array_reverse($matched_macros, true) as $pos => $macro) { 569 $param = substr_replace($param, $macros[$macro], $pos, strlen($macro)); 570 } 571 572 $param = quoteFunctionParam($param, $forced); 573 break; 574 } 575 576 $function_chain = substr_replace($function_chain, $param, $param_raw['pos'], strlen($param_raw['raw'])); 577 } 578 579 $function = substr_replace($function, $function_chain, $params_raw['pos'], strlen($params_raw['raw'])); 580 } 581 582 return $function; 583 } 584 585 /** 586 * Find function ids in trigger expression. 587 * 588 * @param string $expression 589 * 590 * @return array where key is function id position in expression and value is function id 591 */ 592 protected function findFunctions($expression) { 593 $functionids = []; 594 595 $functionid_parser = new CFunctionIdParser(); 596 $macro_parser = new CMacroParser(['{TRIGGER.VALUE}']); 597 $user_macro_parser = new CUserMacroParser(); 598 599 for ($pos = 0, $i = 1; isset($expression[$pos]); $pos++) { 600 if ($functionid_parser->parse($expression, $pos) != CParser::PARSE_FAIL) { 601 $pos += $functionid_parser->getLength() - 1; 602 $functionids[$i++] = substr($functionid_parser->getMatch(), 1, -1); 603 } 604 elseif ($user_macro_parser->parse($expression, $pos) != CParser::PARSE_FAIL) { 605 $pos += $user_macro_parser->getLength() - 1; 606 } 607 elseif ($macro_parser->parse($expression, $pos) != CParser::PARSE_FAIL) { 608 $pos += $macro_parser->getLength() - 1; 609 } 610 } 611 612 if (array_key_exists(1, $functionids)) { 613 $functionids[0] = $functionids[1]; 614 } 615 616 return $functionids; 617 } 618 619 /** 620 * Get interface macros. 621 * 622 * @param array $macros 623 * @param array $macros[<functionid>] 624 * @param array $macros[<functionid>][<macro>] an array of the tokens 625 * @param array $macro_values 626 * 627 * @return array 628 */ 629 protected function getIpMacros(array $macros, array $macro_values) { 630 if (!$macros) { 631 return $macro_values; 632 } 633 634 $result = DBselect( 635 'SELECT f.triggerid,f.functionid,n.ip,n.dns,n.type,n.useip,n.port'. 636 ' FROM functions f'. 637 ' JOIN items i ON f.itemid=i.itemid'. 638 ' JOIN interface n ON i.hostid=n.hostid'. 639 ' WHERE '.dbConditionInt('f.functionid', array_keys($macros)). 640 ' AND n.main=1' 641 ); 642 643 // Macro should be resolved to interface with highest priority ($priorities). 644 $interfaces = []; 645 646 while ($row = DBfetch($result)) { 647 if (array_key_exists($row['functionid'], $interfaces) 648 && $this->interfacePriorities[$interfaces[$row['functionid']]['type']] 649 > $this->interfacePriorities[$row['type']]) { 650 continue; 651 } 652 653 $interfaces[$row['functionid']] = $row; 654 } 655 656 foreach ($interfaces as $interface) { 657 foreach ($macros[$interface['functionid']] as $macro => $tokens) { 658 switch ($macro) { 659 case 'IPADDRESS': 660 case 'HOST.IP': 661 $value = $interface['ip']; 662 break; 663 case 'HOST.DNS': 664 $value = $interface['dns']; 665 break; 666 case 'HOST.CONN': 667 $value = $interface['useip'] ? $interface['ip'] : $interface['dns']; 668 break; 669 case 'HOST.PORT': 670 $value = $interface['port']; 671 break; 672 } 673 674 foreach ($tokens as $token) { 675 $macro_values[$interface['triggerid']][$token['token']] = $value; 676 } 677 } 678 } 679 680 return $macro_values; 681 } 682 683 /** 684 * Get item macros. 685 * 686 * @param array $macros 687 * @param array $macros[<functionid>] 688 * @param array $macros[<functionid>][<macro>] An array of the tokens. 689 * @param array $macro_values 690 * @param array $triggers 691 * @param bool $events Resolve {ITEM.VALUE} macro using 'clock' and 'ns' fields. 692 * 693 * @return array 694 */ 695 protected function getItemMacros(array $macros, array $macro_values, array $triggers = [], $events = false) { 696 if (!$macros) { 697 return $macro_values; 698 } 699 700 $functions = DBfetchArray(DBselect( 701 'SELECT f.triggerid,f.functionid,i.itemid,i.value_type,i.units,i.valuemapid'. 702 ' FROM functions f'. 703 ' JOIN items i ON f.itemid=i.itemid'. 704 ' JOIN hosts h ON i.hostid=h.hostid'. 705 ' WHERE '.dbConditionInt('f.functionid', array_keys($macros)) 706 )); 707 708 // False passed to DBfetch to get data without null converted to 0, which is done by default. 709 foreach ($functions as $function) { 710 foreach ($macros[$function['functionid']] as $m => $tokens) { 711 switch ($m) { 712 case 'ITEM.VALUE': 713 if ($events) { 714 $trigger = $triggers[$function['triggerid']]; 715 $value = Manager::History()->getValueAt($function, $trigger['clock'], $trigger['ns']); 716 break; 717 } 718 // break; is not missing here 719 720 case 'ITEM.LASTVALUE': 721 $history = Manager::History()->getLastValues([$function], 1, ZBX_HISTORY_PERIOD); 722 723 $value = array_key_exists($function['itemid'], $history) 724 ? $history[$function['itemid']][0]['value'] 725 : null; 726 break; 727 } 728 729 if ($value !== null) { 730 foreach ($tokens as $token) { 731 if (array_key_exists('function', $token)) { 732 if ($token['function'] !== 'regsub' && $token['function'] !== 'iregsub') { 733 continue; 734 } 735 736 if (count($token['parameters']) != 2) { 737 continue; 738 } 739 740 $ci = ($token['function'] === 'iregsub') ? 'i' : ''; 741 742 set_error_handler(function ($errno, $errstr) {}); 743 $rc = preg_match('/'.$token['parameters'][0].'/'.$ci, $value, $matches); 744 restore_error_handler(); 745 746 if ($rc === false) { 747 continue; 748 } 749 750 $macro_value = $token['parameters'][1]; 751 $matched_macros = $this->getMacroPositions($macro_value, ['replacements' => true]); 752 753 foreach (array_reverse($matched_macros, true) as $pos => $macro) { 754 $macro_value = substr_replace($macro_value, 755 array_key_exists($macro[1], $matches) ? $matches[$macro[1]] : '', 756 $pos, strlen($macro) 757 ); 758 } 759 } 760 else { 761 $macro_value = formatHistoryValue($value, $function); 762 } 763 764 $macro_values[$function['triggerid']][$token['token']] = $macro_value; 765 } 766 } 767 } 768 } 769 770 return $macro_values; 771 } 772 773 /** 774 * Get host macros. 775 * 776 * @param array $macros 777 * @param array $macros[<functionid>] 778 * @param array $macros[<functionid>][<macro>] an array of the tokens 779 * @param array $macro_values 780 * 781 * @return array 782 */ 783 protected function getHostMacros(array $macros, array $macro_values) { 784 if (!$macros) { 785 return $macro_values; 786 } 787 788 $result = DBselect( 789 'SELECT f.triggerid,f.functionid,h.hostid,h.host,h.name'. 790 ' FROM functions f'. 791 ' JOIN items i ON f.itemid=i.itemid'. 792 ' JOIN hosts h ON i.hostid=h.hostid'. 793 ' WHERE '.dbConditionInt('f.functionid', array_keys($macros)) 794 ); 795 796 while ($row = DBfetch($result)) { 797 foreach ($macros[$row['functionid']] as $macro => $tokens) { 798 switch ($macro) { 799 case 'HOST.ID': 800 $value = $row['hostid']; 801 break; 802 803 case 'HOSTNAME': 804 case 'HOST.HOST': 805 $value = $row['host']; 806 break; 807 808 case 'HOST.NAME': 809 $value = $row['name']; 810 break; 811 } 812 813 foreach ($tokens as $token) { 814 $macro_values[$row['triggerid']][$token['token']] = $value; 815 } 816 } 817 } 818 819 return $macro_values; 820 } 821 822 /** 823 * Is type available. 824 * 825 * @param string $type 826 * 827 * @return bool 828 */ 829 protected function isTypeAvailable($type) { 830 return in_array($type, $this->configs[$this->config]['types']); 831 } 832 833 /** 834 * Get source field. 835 * 836 * @return string 837 */ 838 protected function getSource() { 839 return $this->configs[$this->config]['source']; 840 } 841 842 /** 843 * Get macros with values. 844 * 845 * @param array $data 846 * @param array $data[n]['hostids'] the list of host ids; [<hostid1>, ...] 847 * @param array $data[n]['macros'] the list of user macros to resolve, ['<usermacro1>' => null, ...] 848 * 849 * @return array 850 */ 851 protected function getUserMacros(array $data) { 852 if (!$data) { 853 return $data; 854 } 855 856 // User macros. 857 $hostids = []; 858 foreach ($data as $element) { 859 foreach ($element['hostids'] as $hostid) { 860 $hostids[$hostid] = true; 861 } 862 } 863 864 $user_macro_parser = new CUserMacroParser(); 865 866 /* 867 * @var array $host_templates 868 * @var array $host_templates[<hostid>] array of templates 869 */ 870 $host_templates = []; 871 872 /* 873 * @var array $host_macros 874 * @var array $host_macros[<hostid>] 875 * @var array $host_macros[<hostid>][<macro>] macro base without curly braces 876 * @var string $host_macros[<hostid>][<macro>]['value'] base macro value (without context); can be null 877 * @var array $host_macros[<hostid>][<macro>]['contexts'] context values; ['<context>' => '<value>', ...] 878 */ 879 $host_macros = []; 880 881 if ($hostids) { 882 do { 883 $hostids = array_keys($hostids); 884 885 $db_host_macros = DBselect( 886 'SELECT hm.hostid,hm.macro,hm.value'. 887 ' FROM hostmacro hm'. 888 ' WHERE '.dbConditionInt('hm.hostid', $hostids) 889 ); 890 while ($db_host_macro = DBfetch($db_host_macros)) { 891 if ($user_macro_parser->parse($db_host_macro['macro']) != CParser::PARSE_SUCCESS) { 892 continue; 893 } 894 895 $macro = $user_macro_parser->getMacro(); 896 $context = $user_macro_parser->getContext(); 897 898 if (!array_key_exists($db_host_macro['hostid'], $host_macros)) { 899 $host_macros[$db_host_macro['hostid']] = []; 900 } 901 902 if (!array_key_exists($macro, $host_macros[$db_host_macro['hostid']])) { 903 $host_macros[$db_host_macro['hostid']][$macro] = ['value' => null, 'contexts' => []]; 904 } 905 906 if ($context === null) { 907 $host_macros[$db_host_macro['hostid']][$macro]['value'] = $db_host_macro['value']; 908 } 909 else { 910 $host_macros[$db_host_macro['hostid']][$macro]['contexts'][$context] = $db_host_macro['value']; 911 } 912 } 913 914 foreach ($hostids as $hostid) { 915 $host_templates[$hostid] = []; 916 } 917 918 $templateids = []; 919 $db_host_templates = DBselect( 920 'SELECT ht.hostid,ht.templateid'. 921 ' FROM hosts_templates ht'. 922 ' WHERE '.dbConditionInt('ht.hostid', $hostids) 923 ); 924 while ($db_host_template = DBfetch($db_host_templates)) { 925 $host_templates[$db_host_template['hostid']][] = $db_host_template['templateid']; 926 $templateids[$db_host_template['templateid']] = true; 927 } 928 929 // only unprocessed templates will be populated 930 $hostids = []; 931 foreach (array_keys($templateids) as $templateid) { 932 if (!array_key_exists($templateid, $host_templates)) { 933 $hostids[$templateid] = true; 934 } 935 } 936 } while ($hostids); 937 } 938 939 $all_macros_resolved = true; 940 941 foreach ($data as &$element) { 942 $hostids = []; 943 foreach ($element['hostids'] as $hostid) { 944 $hostids[$hostid] = true; 945 } 946 947 $hostids = array_keys($hostids); 948 natsort($hostids); 949 950 foreach ($element['macros'] as $usermacro => &$value) { 951 if ($user_macro_parser->parse($usermacro) == CParser::PARSE_SUCCESS) { 952 $value = $this->getHostUserMacros($hostids, $user_macro_parser->getMacro(), 953 $user_macro_parser->getContext(), $host_templates, $host_macros 954 ); 955 956 if ($value['value'] === null) { 957 $all_macros_resolved = false; 958 } 959 } 960 else { 961 // This macro cannot be resolved. 962 $value = ['value' => $usermacro, 'value_default' => null]; 963 } 964 } 965 unset($value); 966 } 967 unset($element); 968 969 if (!$all_macros_resolved) { 970 // Global macros. 971 $db_global_macros = API::UserMacro()->get([ 972 'output' => ['macro', 'value'], 973 'globalmacro' => true 974 ]); 975 976 /* 977 * @var array $global_macros 978 * @var array $global_macros[<macro>] macro base without curly braces 979 * @var string $global_macros[<macro>]['value'] base macro value (without context); can be null 980 * @var array $global_macros[<macro>]['contexts'] context values; ['<context1>' => '<value1>', ...] 981 */ 982 $global_macros = []; 983 984 foreach ($db_global_macros as $db_global_macro) { 985 if ($user_macro_parser->parse($db_global_macro['macro']) == CParser::PARSE_SUCCESS) { 986 $macro = $user_macro_parser->getMacro(); 987 $context = $user_macro_parser->getContext(); 988 989 if (!array_key_exists($macro, $global_macros)) { 990 $global_macros[$macro] = ['value' => null, 'contexts' => []]; 991 } 992 993 if ($context === null) { 994 $global_macros[$macro]['value'] = $db_global_macro['value']; 995 } 996 else { 997 $global_macros[$macro]['contexts'][$context] = $db_global_macro['value']; 998 } 999 } 1000 } 1001 1002 foreach ($data as &$element) { 1003 foreach ($element['macros'] as $usermacro => &$value) { 1004 if ($value['value'] === null && $user_macro_parser->parse($usermacro) == CParser::PARSE_SUCCESS) { 1005 $macro = $user_macro_parser->getMacro(); 1006 $context = $user_macro_parser->getContext(); 1007 1008 if (array_key_exists($macro, $global_macros)) { 1009 if ($context !== null && array_key_exists($context, $global_macros[$macro]['contexts'])) { 1010 $value['value'] = $global_macros[$macro]['contexts'][$context]; 1011 } 1012 elseif ($global_macros[$macro]['value'] !== null) { 1013 if ($context === null) { 1014 $value['value'] = $global_macros[$macro]['value']; 1015 } 1016 elseif ($value['value_default'] === null) { 1017 $value['value_default'] = $global_macros[$macro]['value']; 1018 } 1019 } 1020 } 1021 } 1022 } 1023 unset($value); 1024 } 1025 unset($element); 1026 } 1027 1028 foreach ($data as &$element) { 1029 foreach ($element['macros'] as $usermacro => &$value) { 1030 if ($value['value'] !== null) { 1031 $value = $value['value']; 1032 } 1033 elseif ($value['value_default'] !== null) { 1034 $value = $value['value_default']; 1035 } 1036 else { 1037 // Unresolved macro. 1038 $value = $usermacro; 1039 } 1040 } 1041 unset($value); 1042 } 1043 unset($element); 1044 1045 return $data; 1046 } 1047 1048 /** 1049 * Get user macro from the requested hosts. 1050 * 1051 * Use the base value returned by host macro as default value when expanding expand global macro. This will ensure 1052 * the following user macro resolving priority: 1053 * 1) host/template context macro 1054 * 2) global context macro 1055 * 3) host/template base (default) macro 1056 * 4) global base (default) macro 1057 * 1058 * @param array $hostids The sorted list of hosts where macros will be looked for (hostid => hostid) 1059 * @param string $macro Macro base without curly braces, for example: SNMP_COMMUNITY 1060 * @param string $context Macro context to resolve 1061 * @param array $host_templates The list of linked templates (see getUserMacros() for more details) 1062 * @param array $host_macros The list of macros on hosts (see getUserMacros() for more details) 1063 * @param string $value_default Value 1064 * 1065 * @return array 1066 */ 1067 private function getHostUserMacros(array $hostids, $macro, $context, array $host_templates, array $host_macros, 1068 $value_default = null) { 1069 foreach ($hostids as $hostid) { 1070 if (array_key_exists($hostid, $host_macros) && array_key_exists($macro, $host_macros[$hostid])) { 1071 if ($context !== null && array_key_exists($context, $host_macros[$hostid][$macro]['contexts'])) { 1072 return [ 1073 'value' => $host_macros[$hostid][$macro]['contexts'][$context], 1074 'value_default' => $value_default 1075 ]; 1076 } 1077 1078 if ($host_macros[$hostid][$macro]['value'] !== null) { 1079 if ($context === null) { 1080 return ['value' => $host_macros[$hostid][$macro]['value'], 'value_default' => $value_default]; 1081 } 1082 elseif ($value_default === null) { 1083 $value_default = $host_macros[$hostid][$macro]['value']; 1084 } 1085 } 1086 } 1087 } 1088 1089 if (!$host_templates) { 1090 return ['value' => null, 'value_default' => $value_default]; 1091 } 1092 1093 $templateids = []; 1094 1095 foreach ($hostids as $hostid) { 1096 if (array_key_exists($hostid, $host_templates)) { 1097 foreach ($host_templates[$hostid] as $templateid) { 1098 $templateids[$templateid] = true; 1099 } 1100 } 1101 } 1102 1103 if ($templateids) { 1104 $templateids = array_keys($templateids); 1105 natsort($templateids); 1106 1107 return $this->getHostUserMacros($templateids, $macro, $context, $host_templates, $host_macros, 1108 $value_default 1109 ); 1110 } 1111 1112 return ['value' => null, 'value_default' => $value_default]; 1113 } 1114} 1115