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 $values = []; 57 $expression_data = new CTriggerExpression(['collapsed_expression' => true]); 58 59 if (($result = $expression_data->parse($expression)) !== false) { 60 foreach ($result->getTokens() as $token) { 61 switch ($token['type']) { 62 case CTriggerExprParserResult::TOKEN_TYPE_NUMBER: 63 case CTriggerExprParserResult::TOKEN_TYPE_USER_MACRO: 64 $values[] = $token['value']; 65 break; 66 67 case CTriggerExprParserResult::TOKEN_TYPE_STRING: 68 $values[] = $token['data']['string']; 69 break; 70 } 71 } 72 } 73 74 foreach ($references as $macro => $value) { 75 $i = (int) $macro[1] - 1; 76 $references[$macro] = array_key_exists($i, $values) ? $values[$i] : ''; 77 } 78 79 return $references; 80 } 81 82 /** 83 * Checking existence of the macros. 84 * 85 * @param array $texts 86 * @param array $type 87 * 88 * @return bool 89 */ 90 protected function hasMacros(array $texts, array $types) { 91 foreach ($texts as $text) { 92 if ($this->getMacroPositions($text, $types)) { 93 return true; 94 } 95 } 96 97 return false; 98 } 99 100 /** 101 * Transform types, used in extractMacros() function to types which can be used in getMacroPositions(). 102 * 103 * @param array $types 104 * 105 * @return array 106 */ 107 protected function transformToPositionTypes(array $types) { 108 foreach (['macros', 'macros_n', 'macros_an', 'macro_funcs_n'] as $type) { 109 if (array_key_exists($type, $types)) { 110 $patterns = []; 111 foreach ($types[$type] as $key => $_patterns) { 112 $patterns = array_merge($patterns, $_patterns); 113 } 114 $types[$type] = $patterns; 115 } 116 } 117 118 return $types; 119 } 120 121 /** 122 * Extract positions of the macros from a string. 123 * 124 * @param string $text 125 * @param array $types 126 * @param bool $types['usermacros'] 127 * @param array $types['macros'][<macro_patterns>] 128 * @param array $types['macros_n'][<macro_patterns>] 129 * @param array $types['macros_an'][<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_macros_an = array_key_exists('macros_an', $types); 144 $extract_macro_funcs_n = array_key_exists('macro_funcs_n', $types); 145 $extract_references = array_key_exists('references', $types); 146 $extract_lldmacros = array_key_exists('lldmacros', $types); 147 $extract_functionids = array_key_exists('functionids', $types); 148 $extract_replacements = array_key_exists('replacements', $types); 149 150 if ($extract_usermacros) { 151 $user_macro_parser = new CUserMacroParser(); 152 } 153 154 if ($extract_macros) { 155 $macro_parser = new CMacroParser($types['macros']); 156 } 157 158 if ($extract_macros_n) { 159 $macro_n_parser = new CMacroParser($types['macros_n'], ['ref_type' => CMacroParser::REFERENCE_NUMERIC]); 160 } 161 162 if ($extract_macros_an) { 163 $macro_an_parser = new CMacroParser($types['macros_an'], 164 ['ref_type' => CMacroParser::REFERENCE_ALPHANUMERIC] 165 ); 166 } 167 168 if ($extract_macro_funcs_n) { 169 $macro_func_n_parser = new CMacroFunctionParser($types['macro_funcs_n'], 170 ['ref_type' => CMacroParser::REFERENCE_NUMERIC] 171 ); 172 } 173 174 if ($extract_references) { 175 $reference_parser = new CReferenceParser(); 176 } 177 178 if ($extract_lldmacros) { 179 $lld_macro_parser = new CLLDMacroParser(); 180 $lld_macro_function_parser = new CLLDMacroFunctionParser(); 181 } 182 183 if ($extract_functionids) { 184 $functionid_parser = new CFunctionIdParser(); 185 } 186 187 if ($extract_replacements) { 188 $replacement_parser = new CReplacementParser(); 189 } 190 191 for ($pos = 0; isset($text[$pos]); $pos++) { 192 if ($extract_usermacros && $user_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 193 $macros[$pos] = $user_macro_parser->getMatch(); 194 $pos += $user_macro_parser->getLength() - 1; 195 } 196 elseif ($extract_macros && $macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 197 $macros[$pos] = $macro_parser->getMatch(); 198 $pos += $macro_parser->getLength() - 1; 199 } 200 elseif ($extract_macros_n && $macro_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 201 $macros[$pos] = $macro_n_parser->getMatch(); 202 $pos += $macro_n_parser->getLength() - 1; 203 } 204 elseif ($extract_macros_an && $macro_an_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 205 $macros[$pos] = $macro_an_parser->getMatch(); 206 $pos += $macro_an_parser->getLength() - 1; 207 } 208 elseif ($extract_macro_funcs_n && $macro_func_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 209 $macros[$pos] = $macro_func_n_parser->getMatch(); 210 $pos += $macro_func_n_parser->getLength() - 1; 211 } 212 elseif ($extract_references && $reference_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 213 $macros[$pos] = $reference_parser->getMatch(); 214 $pos += $reference_parser->getLength() - 1; 215 } 216 elseif ($extract_lldmacros && $lld_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 217 $macros[$pos] = $lld_macro_parser->getMatch(); 218 $pos += $lld_macro_parser->getLength() - 1; 219 } 220 elseif ($extract_lldmacros && $lld_macro_function_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 221 $macros[$pos] = $lld_macro_function_parser->getMatch(); 222 $pos += $lld_macro_function_parser->getLength() - 1; 223 } 224 elseif ($extract_functionids && $functionid_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 225 $macros[$pos] = $functionid_parser->getMatch(); 226 $pos += $functionid_parser->getLength() - 1; 227 } 228 elseif ($extract_replacements && $replacement_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 229 $macros[$pos] = $replacement_parser->getMatch(); 230 $pos += $replacement_parser->getLength() - 1; 231 } 232 } 233 234 return $macros; 235 } 236 237 /** 238 * Extract macros from a string. 239 * 240 * @param array $texts 241 * @param array $types 242 * @param bool $types['usermacros'] 243 * @param array $types['macros'][][<macro_patterns>] 244 * @param array $types['macros_n'][][<macro_patterns>] 245 * @param array $types['macros_an'][][<macro_patterns>] 246 * @param array $types['macro_funcs_n'][][<macro_patterns>] 247 * @param bool $types['references'] 248 * @param bool $types['lldmacros'] 249 * @param bool $types['functionids'] 250 * 251 * @return array 252 */ 253 public static function extractMacros(array $texts, array $types) { 254 $macros = []; 255 $extract_usermacros = array_key_exists('usermacros', $types); 256 $extract_macros = array_key_exists('macros', $types); 257 $extract_macros_n = array_key_exists('macros_n', $types); 258 $extract_macros_an = array_key_exists('macros_an', $types); 259 $extract_macro_funcs_n = array_key_exists('macro_funcs_n', $types); 260 $extract_references = array_key_exists('references', $types); 261 $extract_lldmacros = array_key_exists('lldmacros', $types); 262 $extract_functionids = array_key_exists('functionids', $types); 263 264 if ($extract_usermacros) { 265 $macros['usermacros'] = []; 266 267 $user_macro_parser = new CUserMacroParser(); 268 } 269 270 if ($extract_macros) { 271 $macros['macros'] = []; 272 273 foreach ($types['macros'] as $key => $macro_patterns) { 274 $types['macros'][$key] = new CMacroParser($macro_patterns); 275 $macros['macros'][$key] = []; 276 } 277 } 278 279 if ($extract_macros_n) { 280 $macros['macros_n'] = []; 281 282 foreach ($types['macros_n'] as $key => $macro_patterns) { 283 $types['macros_n'][$key] = new CMacroParser($macro_patterns, 284 ['ref_type' => CMacroParser::REFERENCE_NUMERIC] 285 ); 286 $macros['macros_n'][$key] = []; 287 } 288 } 289 290 if ($extract_macros_an) { 291 $macros['macros_an'] = []; 292 293 foreach ($types['macros_an'] as $key => $macro_patterns) { 294 $types['macros_an'][$key] = new CMacroParser($macro_patterns, 295 ['ref_type' => CMacroParser::REFERENCE_ALPHANUMERIC] 296 ); 297 $macros['macros_an'][$key] = []; 298 } 299 } 300 301 if ($extract_macro_funcs_n) { 302 $macros['macro_funcs_n'] = []; 303 304 foreach ($types['macro_funcs_n'] as $key => $macro_patterns) { 305 $types['macro_funcs_n'][$key] = new CMacroFunctionParser($macro_patterns, 306 ['ref_type' => CMacroParser::REFERENCE_NUMERIC] 307 ); 308 $macros['macro_funcs_n'][$key] = []; 309 } 310 } 311 312 if ($extract_references) { 313 $macros['references'] = []; 314 315 $reference_parser = new CReferenceParser(); 316 } 317 318 if ($extract_lldmacros) { 319 $macros['lldmacros'] = []; 320 321 $lld_macro_parser = new CLLDMacroParser(); 322 $lld_macro_function_parser = new CLLDMacroFunctionParser(); 323 } 324 325 if ($extract_functionids) { 326 $macros['functionids'] = []; 327 328 $functionid_parser = new CFunctionIdParser(); 329 } 330 331 foreach ($texts as $text) { 332 for ($pos = 0; isset($text[$pos]); $pos++) { 333 if ($extract_usermacros && $user_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 334 $macros['usermacros'][$user_macro_parser->getMatch()] = null; 335 $pos += $user_macro_parser->getLength() - 1; 336 continue; 337 } 338 339 if ($extract_macros) { 340 foreach ($types['macros'] as $key => $macro_parser) { 341 if ($macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 342 $macros['macros'][$key][$macro_parser->getMatch()] = true; 343 $pos += $macro_parser->getLength() - 1; 344 continue 2; 345 } 346 } 347 } 348 349 if ($extract_macros_n) { 350 foreach ($types['macros_n'] as $key => $macro_n_parser) { 351 if ($macro_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 352 $macros['macros_n'][$key][$macro_n_parser->getMatch()] = [ 353 'macro' => $macro_n_parser->getMacro(), 354 'f_num' => $macro_n_parser->getReference() 355 ]; 356 $pos += $macro_n_parser->getLength() - 1; 357 continue 2; 358 } 359 } 360 } 361 362 if ($extract_macros_an) { 363 foreach ($types['macros_an'] as $key => $macro_an_parser) { 364 if ($macro_an_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 365 $macros['macros_an'][$key][$macro_an_parser->getMatch()] = [ 366 'macro' => $macro_an_parser->getMacro(), 367 'f_num' => $macro_an_parser->getReference() 368 ]; 369 $pos += $macro_an_parser->getLength() - 1; 370 continue 2; 371 } 372 } 373 } 374 375 if ($extract_macro_funcs_n) { 376 foreach ($types['macro_funcs_n'] as $key => $macro_func_n_parser) { 377 if ($macro_func_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 378 $macro_n_parser = $macro_func_n_parser->getMacroParser(); 379 $function_parser = $macro_func_n_parser->getFunctionParser(); 380 $function_parameters = []; 381 382 foreach ($function_parser->getParamsRaw()['parameters'] as $param_raw) { 383 switch ($param_raw['type']) { 384 case CFunctionParser::PARAM_UNQUOTED: 385 $function_parameters[] = $param_raw['raw']; 386 break; 387 388 case CFunctionParser::PARAM_QUOTED: 389 $function_parameters[] = CFunctionParser::unquoteParam($param_raw['raw']); 390 break; 391 } 392 } 393 394 $macros['macro_funcs_n'][$key][$macro_func_n_parser->getMatch()] = [ 395 'macro' => $macro_n_parser->getMacro(), 396 'f_num' => $macro_n_parser->getReference(), 397 'function' => $function_parser->getFunction(), 398 'parameters' => $function_parameters 399 ]; 400 $pos += $macro_func_n_parser->getLength() - 1; 401 continue 2; 402 } 403 } 404 } 405 406 if ($extract_references && $reference_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 407 $macros['references'][$reference_parser->getMatch()] = null; 408 $pos += $reference_parser->getLength() - 1; 409 continue; 410 } 411 412 if ($extract_lldmacros) { 413 if ($lld_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 414 $macros['lldmacros'][$lld_macro_parser->getMatch()] = null; 415 $pos += $lld_macro_parser->getLength() - 1; 416 continue; 417 } 418 elseif ($lld_macro_function_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 419 $macros['lldmacros'][$lld_macro_function_parser->getMatch()] = null; 420 $pos += $lld_macro_function_parser->getLength() - 1; 421 continue; 422 } 423 } 424 425 if ($extract_functionids && $functionid_parser->parse($text, $pos) != CParser::PARSE_FAIL) { 426 $macros['functionids'][$functionid_parser->getMatch()] = null; 427 $pos += $functionid_parser->getLength() - 1; 428 continue; 429 } 430 } 431 } 432 433 if ($extract_macros) { 434 foreach ($types['macros'] as $key => $macro_parser) { 435 $macros['macros'][$key] = array_keys($macros['macros'][$key]); 436 } 437 } 438 439 return $macros; 440 } 441 442 /** 443 * Returns the list of the item key parameters. 444 * 445 * @param string $params_raw 446 * 447 * @return array 448 */ 449 private function getItemKeyParameters($params_raw) { 450 $item_key_parameters = []; 451 452 foreach ($params_raw as $param_raw) { 453 switch ($param_raw['type']) { 454 case CItemKey::PARAM_ARRAY: 455 $item_key_parameters = array_merge($item_key_parameters, 456 $this->getItemKeyParameters($param_raw['parameters']) 457 ); 458 break; 459 460 case CItemKey::PARAM_UNQUOTED: 461 $item_key_parameters[] = $param_raw['raw']; 462 break; 463 464 case CItemKey::PARAM_QUOTED: 465 $item_key_parameters[] = CItemKey::unquoteParam($param_raw['raw']); 466 break; 467 } 468 } 469 470 return $item_key_parameters; 471 } 472 473 /** 474 * Extract macros from an item key. 475 * 476 * @param string $key an item key 477 * @param array $types the types of macros (see extractMacros() for more details) 478 * 479 * @return array see extractMacros() for more details 480 */ 481 protected function extractItemKeyMacros($key, array $types) { 482 $item_key_parser = new CItemKey(); 483 484 $item_key_parameters = []; 485 if ($item_key_parser->parse($key) == CParser::PARSE_SUCCESS) { 486 $item_key_parameters = $this->getItemKeyParameters($item_key_parser->getParamsRaw()); 487 } 488 489 return self::extractMacros($item_key_parameters, $types); 490 } 491 492 /** 493 * Extract macros from a trigger function. 494 * 495 * @param string $function a trigger function, for example 'last({$LAST})' 496 * @param array $types the types of macros (see extractMacros() for more details) 497 * 498 * @return array see extractMacros() for more details 499 */ 500 protected function extractFunctionMacros($function, array $types) { 501 $function_parser = new CFunctionParser(); 502 503 $function_parameters = []; 504 if ($function_parser->parse($function) == CParser::PARSE_SUCCESS) { 505 foreach ($function_parser->getParamsRaw()['parameters'] as $param_raw) { 506 switch ($param_raw['type']) { 507 case CFunctionParser::PARAM_UNQUOTED: 508 $function_parameters[] = $param_raw['raw']; 509 break; 510 511 case CFunctionParser::PARAM_QUOTED: 512 $function_parameters[] = CFunctionParser::unquoteParam($param_raw['raw']); 513 break; 514 } 515 } 516 } 517 518 return self::extractMacros($function_parameters, $types); 519 } 520 521 /** 522 * Resolves macros in the item key parameters. 523 * 524 * @param string $key_chain an item key chain 525 * @param string $params_raw 526 * @param array $macros the list of macros (['{<MACRO>}' => '<value>', ...]) 527 * @param array $types the types of macros (see getMacroPositions() for more details) 528 * 529 * @return string 530 */ 531 private function resolveItemKeyParamsMacros($key_chain, array $params_raw, array $macros, array $types) { 532 foreach (array_reverse($params_raw) as $param_raw) { 533 $param = $param_raw['raw']; 534 $forced = false; 535 536 switch ($param_raw['type']) { 537 case CItemKey::PARAM_ARRAY: 538 $param = $this->resolveItemKeyParamsMacros($param, $param_raw['parameters'], $macros, $types); 539 break; 540 541 case CItemKey::PARAM_QUOTED: 542 $param = CItemKey::unquoteParam($param); 543 $forced = true; 544 // break; is not missing here 545 546 case CItemKey::PARAM_UNQUOTED: 547 $matched_macros = $this->getMacroPositions($param, $types); 548 549 foreach (array_reverse($matched_macros, true) as $pos => $macro) { 550 $param = substr_replace($param, $macros[$macro], $pos, strlen($macro)); 551 } 552 553 $param = quoteItemKeyParam($param, $forced); 554 break; 555 } 556 557 $key_chain = substr_replace($key_chain, $param, $param_raw['pos'], strlen($param_raw['raw'])); 558 } 559 560 return $key_chain; 561 } 562 563 /** 564 * Resolves macros in the item key. 565 * 566 * @param string $key an item key 567 * @param array $macros the list of macros (['{<MACRO>}' => '<value>', ...]) 568 * @param array $types the types of macros (see getMacroPositions() for more details) 569 * 570 * @return string 571 */ 572 protected function resolveItemKeyMacros($key, array $macros, array $types) { 573 $item_key_parser = new CItemKey(); 574 575 if ($item_key_parser->parse($key) == CParser::PARSE_SUCCESS) { 576 $key = $this->resolveItemKeyParamsMacros($key, $item_key_parser->getParamsRaw(), $macros, $types); 577 } 578 579 return $key; 580 } 581 582 /** 583 * Resolves macros in the trigger function parameters. 584 * 585 * @param string $function a trigger function 586 * @param array $macros the list of macros (['{<MACRO>}' => '<value>', ...]) 587 * @param array $types the types of macros (see getMacroPositions() for more details) 588 * 589 * @return string 590 */ 591 protected function resolveFunctionMacros($function, array $macros, array $types) { 592 $function_parser = new CFunctionParser(); 593 594 if ($function_parser->parse($function) == CParser::PARSE_SUCCESS) { 595 $params_raw = $function_parser->getParamsRaw(); 596 $function_chain = $params_raw['raw']; 597 598 foreach (array_reverse($params_raw['parameters']) as $param_raw) { 599 $param = $param_raw['raw']; 600 $forced = false; 601 602 switch ($param_raw['type']) { 603 case CFunctionParser::PARAM_QUOTED: 604 $param = CFunctionParser::unquoteParam($param); 605 $forced = true; 606 // break; is not missing here 607 608 case CFunctionParser::PARAM_UNQUOTED: 609 $matched_macros = $this->getMacroPositions($param, $types); 610 611 foreach (array_reverse($matched_macros, true) as $pos => $macro) { 612 $param = substr_replace($param, $macros[$macro], $pos, strlen($macro)); 613 } 614 615 $param = quoteFunctionParam($param, $forced); 616 break; 617 } 618 619 $function_chain = substr_replace($function_chain, $param, $param_raw['pos'], strlen($param_raw['raw'])); 620 } 621 622 $function = substr_replace($function, $function_chain, $params_raw['pos'], strlen($params_raw['raw'])); 623 } 624 625 return $function; 626 } 627 628 /** 629 * Find function ids in trigger expression. 630 * 631 * @param string $expression 632 * 633 * @return array where key is function id position in expression and value is function id 634 */ 635 protected function findFunctions($expression) { 636 $functionids = []; 637 638 $functionid_parser = new CFunctionIdParser(); 639 $macro_parser = new CMacroParser(['{TRIGGER.VALUE}']); 640 $user_macro_parser = new CUserMacroParser(); 641 642 for ($pos = 0, $i = 1; isset($expression[$pos]); $pos++) { 643 if ($functionid_parser->parse($expression, $pos) != CParser::PARSE_FAIL) { 644 $pos += $functionid_parser->getLength() - 1; 645 $functionids[$i++] = substr($functionid_parser->getMatch(), 1, -1); 646 } 647 elseif ($user_macro_parser->parse($expression, $pos) != CParser::PARSE_FAIL) { 648 $pos += $user_macro_parser->getLength() - 1; 649 } 650 elseif ($macro_parser->parse($expression, $pos) != CParser::PARSE_FAIL) { 651 $pos += $macro_parser->getLength() - 1; 652 } 653 } 654 655 if (array_key_exists(1, $functionids)) { 656 $functionids[0] = $functionids[1]; 657 } 658 659 return $functionids; 660 } 661 662 /** 663 * Get interface macros. 664 * 665 * @param array $macros 666 * @param array $macros[<functionid>] 667 * @param array $macros[<functionid>][<macro>] an array of the tokens 668 * @param array $macro_values 669 * 670 * @return array 671 */ 672 protected function getIpMacros(array $macros, array $macro_values) { 673 if (!$macros) { 674 return $macro_values; 675 } 676 677 $result = DBselect( 678 'SELECT f.triggerid,f.functionid,n.ip,n.dns,n.type,n.useip,n.port'. 679 ' FROM functions f'. 680 ' JOIN items i ON f.itemid=i.itemid'. 681 ' JOIN interface n ON i.hostid=n.hostid'. 682 ' WHERE '.dbConditionInt('f.functionid', array_keys($macros)). 683 ' AND n.main=1' 684 ); 685 686 // Macro should be resolved to interface with highest priority ($priorities). 687 $interfaces = []; 688 689 while ($row = DBfetch($result)) { 690 if (array_key_exists($row['functionid'], $interfaces) 691 && $this->interfacePriorities[$interfaces[$row['functionid']]['type']] 692 > $this->interfacePriorities[$row['type']]) { 693 continue; 694 } 695 696 $interfaces[$row['functionid']] = $row; 697 } 698 699 foreach ($interfaces as $interface) { 700 foreach ($macros[$interface['functionid']] as $macro => $tokens) { 701 switch ($macro) { 702 case 'IPADDRESS': 703 case 'HOST.IP': 704 $value = $interface['ip']; 705 break; 706 case 'HOST.DNS': 707 $value = $interface['dns']; 708 break; 709 case 'HOST.CONN': 710 $value = $interface['useip'] ? $interface['ip'] : $interface['dns']; 711 break; 712 case 'HOST.PORT': 713 $value = $interface['port']; 714 break; 715 } 716 717 foreach ($tokens as $token) { 718 $macro_values[$interface['triggerid']][$token['token']] = $value; 719 } 720 } 721 } 722 723 return $macro_values; 724 } 725 726 /** 727 * Get item macros. 728 * 729 * @param array $macros 730 * @param array $macros[<functionid>] 731 * @param array $macros[<functionid>][<macro>] An array of the tokens. 732 * @param array $macro_values 733 * @param array $triggers 734 * @param array $options 735 * @param bool $options['events] Resolve {ITEM.VALUE} macro using 'clock' and 'ns' fields. 736 * @param bool $options['html] 737 * 738 * @return array 739 */ 740 protected function getItemMacros(array $macros, array $macro_values, array $triggers = [], array $options = []) { 741 if (!$macros) { 742 return $macro_values; 743 } 744 745 $options += [ 746 'events' => false, 747 'html' => false 748 ]; 749 750 $functions = DBfetchArray(DBselect( 751 'SELECT f.triggerid,f.functionid,i.itemid,i.hostid,i.name,i.key_,i.value_type,i.units,i.valuemapid'. 752 ' FROM functions f'. 753 ' JOIN items i ON f.itemid=i.itemid'. 754 ' JOIN hosts h ON i.hostid=h.hostid'. 755 ' WHERE '.dbConditionInt('f.functionid', array_keys($macros)) 756 )); 757 758 $functions = CMacrosResolverHelper::resolveItemNames($functions); 759 760 // False passed to DBfetch to get data without null converted to 0, which is done by default. 761 foreach ($functions as $function) { 762 foreach ($macros[$function['functionid']] as $m => $tokens) { 763 $clock = null; 764 $value = null; 765 766 switch ($m) { 767 case 'ITEM.VALUE': 768 if ($options['events']) { 769 $trigger = $triggers[$function['triggerid']]; 770 $history = Manager::History()->getValueAt($function, $trigger['clock'], $trigger['ns']); 771 772 if (is_array($history)) { 773 if (array_key_exists('clock', $history)) { 774 $clock = $history['clock']; 775 } 776 if (array_key_exists('value', $history)) { 777 $value = $history['value']; 778 } 779 } 780 break; 781 } 782 // break; is not missing here 783 784 case 'ITEM.LASTVALUE': 785 $history = Manager::History()->getLastValues([$function], 1, ZBX_HISTORY_PERIOD); 786 787 if (array_key_exists($function['itemid'], $history)) { 788 $clock = $history[$function['itemid']][0]['clock']; 789 $value = $history[$function['itemid']][0]['value']; 790 } 791 break; 792 } 793 794 foreach ($tokens as $token) { 795 $macro_value = UNRESOLVED_MACRO_STRING; 796 797 if ($value !== null) { 798 if (array_key_exists('function', $token)) { 799 if ($token['function'] !== 'regsub' && $token['function'] !== 'iregsub') { 800 continue; 801 } 802 803 if (count($token['parameters']) != 2) { 804 continue; 805 } 806 807 $ci = ($token['function'] === 'iregsub') ? 'i' : ''; 808 809 set_error_handler(function ($errno, $errstr) {}); 810 $rc = preg_match('/'.$token['parameters'][0].'/'.$ci, $value, $matches); 811 restore_error_handler(); 812 813 if ($rc === false) { 814 continue; 815 } 816 817 $macro_value = $token['parameters'][1]; 818 $matched_macros = $this->getMacroPositions($macro_value, ['replacements' => true]); 819 820 foreach (array_reverse($matched_macros, true) as $pos => $macro) { 821 $macro_value = substr_replace($macro_value, 822 array_key_exists($macro[1], $matches) ? $matches[$macro[1]] : '', 823 $pos, strlen($macro) 824 ); 825 } 826 } 827 else { 828 $macro_value = formatHistoryValue($value, $function); 829 } 830 } 831 832 if ($options['html']) { 833 $macro_value = str_replace(["\r\n", "\n"], [" "], $macro_value); 834 $hint_table = (new CTable()) 835 ->addClass('list-table') 836 ->addRow([ 837 new CCol($function['name_expanded']), 838 new CCol( 839 ($clock !== null) 840 ? zbx_date2str(DATE_TIME_FORMAT_SECONDS, $clock) 841 : UNRESOLVED_MACRO_STRING 842 ), 843 new CCol($macro_value), 844 new CCol( 845 ($function['value_type'] == ITEM_VALUE_TYPE_FLOAT 846 || $function['value_type'] == ITEM_VALUE_TYPE_UINT64) 847 ? new CLink(_('Graph'), (new CUrl('history.php')) 848 ->setArgument('action', HISTORY_GRAPH) 849 ->setArgument('itemids[]', $function['itemid']) 850 ->getUrl() 851 ) 852 : new CLink(_('History'), (new CUrl('history.php')) 853 ->setArgument('action', HISTORY_VALUES) 854 ->setArgument('itemids[]', $function['itemid']) 855 ->getUrl() 856 ) 857 ) 858 ]); 859 $macro_value = new CSpan([ 860 (new CSpan()) 861 ->addClass('main-hint') 862 ->setHint($hint_table), 863 (new CLinkAction($macro_value)) 864 ->addClass('hint-item') 865 ->setAttribute('data-hintbox', '1') 866 ]); 867 } 868 869 $macro_values[$function['triggerid']][$token['token']] = $macro_value; 870 } 871 } 872 } 873 874 return $macro_values; 875 } 876 877 /** 878 * Get host macros. 879 * 880 * @param array $macros 881 * @param array $macros[<functionid>] 882 * @param array $macros[<functionid>][<macro>] an array of the tokens 883 * @param array $macro_values 884 * 885 * @return array 886 */ 887 protected function getHostMacros(array $macros, array $macro_values) { 888 if (!$macros) { 889 return $macro_values; 890 } 891 892 $result = DBselect( 893 'SELECT f.triggerid,f.functionid,h.hostid,h.host,h.name'. 894 ' FROM functions f'. 895 ' JOIN items i ON f.itemid=i.itemid'. 896 ' JOIN hosts h ON i.hostid=h.hostid'. 897 ' WHERE '.dbConditionInt('f.functionid', array_keys($macros)) 898 ); 899 900 while ($row = DBfetch($result)) { 901 foreach ($macros[$row['functionid']] as $macro => $tokens) { 902 switch ($macro) { 903 case 'HOST.ID': 904 $value = $row['hostid']; 905 break; 906 907 case 'HOSTNAME': 908 case 'HOST.HOST': 909 $value = $row['host']; 910 break; 911 912 case 'HOST.NAME': 913 $value = $row['name']; 914 break; 915 } 916 917 foreach ($tokens as $token) { 918 $macro_values[$row['triggerid']][$token['token']] = $value; 919 } 920 } 921 } 922 923 return $macro_values; 924 } 925 926 /** 927 * Is type available. 928 * 929 * @param string $type 930 * 931 * @return bool 932 */ 933 protected function isTypeAvailable($type) { 934 return in_array($type, $this->configs[$this->config]['types']); 935 } 936 937 /** 938 * Get source field. 939 * 940 * @return string 941 */ 942 protected function getSource() { 943 return $this->configs[$this->config]['source']; 944 } 945 946 /** 947 * Get macros with values. 948 * 949 * @param array $data 950 * @param array $data[n]['hostids'] The list of host ids; [<hostid1>, ...]. 951 * @param array $data[n]['macros'] The list of user macros to resolve, ['<usermacro1>' => null, ...]. 952 * @param bool $unset_undefined Unset undefined macros. 953 * 954 * @return array 955 */ 956 protected function getUserMacros(array $data, bool $unset_undefined = false) { 957 if (!$data) { 958 return $data; 959 } 960 961 // User macros. 962 $hostids = []; 963 foreach ($data as $element) { 964 foreach ($element['hostids'] as $hostid) { 965 $hostids[$hostid] = true; 966 } 967 } 968 969 $user_macro_parser = new CUserMacroParser(); 970 971 /* 972 * @var array $host_templates 973 * @var array $host_templates[<hostid>] array of templates 974 */ 975 $host_templates = []; 976 977 /* 978 * @var array $host_macros 979 * @var array $host_macros[<hostid>] 980 * @var array $host_macros[<hostid>][<macro>] macro base without curly braces 981 * @var string $host_macros[<hostid>][<macro>]['value'] base macro value (without context and regex); 982 * can be null 983 * @var array $host_macros[<hostid>][<macro>]['contexts'] context values; ['<context1>' => '<value1>', ...] 984 * @var array $host_macros[<hostid>][<macro>]['regex'] regex values; ['<regex1>' => '<value1>', ...] 985 */ 986 $host_macros = []; 987 988 if ($hostids) { 989 do { 990 $hostids = array_keys($hostids); 991 992 $db_host_macros = API::UserMacro()->get([ 993 'output' => ['macro', 'value', 'type', 'hostid'], 994 'hostids' => $hostids 995 ]); 996 997 foreach ($db_host_macros as $db_host_macro) { 998 if ($user_macro_parser->parse($db_host_macro['macro']) != CParser::PARSE_SUCCESS) { 999 continue; 1000 } 1001 1002 $hostid = $db_host_macro['hostid']; 1003 $macro = $user_macro_parser->getMacro(); 1004 $context = $user_macro_parser->getContext(); 1005 $regex = $user_macro_parser->getRegex(); 1006 $value = self::getMacroValue($db_host_macro); 1007 1008 if (!array_key_exists($hostid, $host_macros)) { 1009 $host_macros[$hostid] = []; 1010 } 1011 1012 if (!array_key_exists($macro, $host_macros[$hostid])) { 1013 $host_macros[$hostid][$macro] = ['value' => null, 'contexts' => [], 'regex' => []]; 1014 } 1015 1016 if ($context === null && $regex === null) { 1017 $host_macros[$hostid][$macro]['value'] = $value; 1018 } 1019 elseif ($regex !== null) { 1020 $host_macros[$hostid][$macro]['regex'][$regex] = $value; 1021 } 1022 else { 1023 $host_macros[$hostid][$macro]['contexts'][$context] = $value; 1024 } 1025 } 1026 1027 foreach ($hostids as $hostid) { 1028 $host_templates[$hostid] = []; 1029 } 1030 1031 $templateids = []; 1032 $db_host_templates = DBselect( 1033 'SELECT ht.hostid,ht.templateid'. 1034 ' FROM hosts_templates ht'. 1035 ' WHERE '.dbConditionInt('ht.hostid', $hostids) 1036 ); 1037 while ($db_host_template = DBfetch($db_host_templates)) { 1038 $host_templates[$db_host_template['hostid']][] = $db_host_template['templateid']; 1039 $templateids[$db_host_template['templateid']] = true; 1040 } 1041 1042 // only unprocessed templates will be populated 1043 $hostids = []; 1044 foreach (array_keys($templateids) as $templateid) { 1045 if (!array_key_exists($templateid, $host_templates)) { 1046 $hostids[$templateid] = true; 1047 } 1048 } 1049 } while ($hostids); 1050 } 1051 1052 // Reordering only regex array. 1053 $host_macros = self::sortRegexHostMacros($host_macros); 1054 1055 $all_macros_resolved = true; 1056 1057 foreach ($data as &$element) { 1058 $hostids = []; 1059 foreach ($element['hostids'] as $hostid) { 1060 $hostids[$hostid] = true; 1061 } 1062 1063 $hostids = array_keys($hostids); 1064 natsort($hostids); 1065 1066 foreach ($element['macros'] as $usermacro => &$value) { 1067 if ($user_macro_parser->parse($usermacro) == CParser::PARSE_SUCCESS) { 1068 $value = $this->getHostUserMacros($hostids, $user_macro_parser->getMacro(), 1069 $user_macro_parser->getContext(), $host_templates, $host_macros 1070 ); 1071 1072 if ($value['value'] === null) { 1073 $all_macros_resolved = false; 1074 } 1075 } 1076 else { 1077 // This macro cannot be resolved. 1078 $value = ['value' => $usermacro, 'value_default' => null]; 1079 } 1080 } 1081 unset($value); 1082 } 1083 unset($element); 1084 1085 if (!$all_macros_resolved) { 1086 // Global macros. 1087 $db_global_macros = API::UserMacro()->get([ 1088 'output' => ['macro', 'value', 'type'], 1089 'globalmacro' => true 1090 ]); 1091 1092 /* 1093 * @var array $global_macros 1094 * @var array $global_macros[<macro>] macro base without curly braces 1095 * @var string $global_macros[<macro>]['value'] base macro value (without context and regex); 1096 * can be null 1097 * @var array $global_macros[<macro>]['contexts'] context values; ['<context1>' => '<value1>', ...] 1098 * @var array $global_macros[<macro>]['regex'] regex values; ['<regex1>' => '<value1>', ...] 1099 */ 1100 $global_macros = []; 1101 1102 foreach ($db_global_macros as $db_global_macro) { 1103 if ($user_macro_parser->parse($db_global_macro['macro']) == CParser::PARSE_SUCCESS) { 1104 $macro = $user_macro_parser->getMacro(); 1105 $context = $user_macro_parser->getContext(); 1106 $regex = $user_macro_parser->getRegex(); 1107 $value = self::getMacroValue($db_global_macro); 1108 1109 if (!array_key_exists($macro, $global_macros)) { 1110 $global_macros[$macro] = ['value' => null, 'contexts' => [], 'regex' => []]; 1111 } 1112 1113 if ($context === null && $regex === null) { 1114 $global_macros[$macro]['value'] = $value; 1115 } 1116 elseif ($regex !== null) { 1117 $global_macros[$macro]['regex'][$regex] = $value; 1118 } 1119 else { 1120 $global_macros[$macro]['contexts'][$context] = $value; 1121 } 1122 } 1123 } 1124 1125 // Reordering only regex array. 1126 $global_macros = self::sortRegexGlobalMacros($global_macros); 1127 1128 foreach ($data as &$element) { 1129 foreach ($element['macros'] as $usermacro => &$value) { 1130 if ($value['value'] === null && $user_macro_parser->parse($usermacro) == CParser::PARSE_SUCCESS) { 1131 $macro = $user_macro_parser->getMacro(); 1132 $context = $user_macro_parser->getContext(); 1133 1134 if (array_key_exists($macro, $global_macros)) { 1135 if ($context !== null && array_key_exists($context, $global_macros[$macro]['contexts'])) { 1136 $value['value'] = $global_macros[$macro]['contexts'][$context]; 1137 } 1138 elseif ($context !== null && count($global_macros[$macro]['regex'])) { 1139 foreach ($global_macros[$macro]['regex'] as $regex => $val) { 1140 if (preg_match('/'.strtr(trim($regex, '/'), ['/' => '\\/']).'/', $context) === 1) { 1141 $value['value'] = $val; 1142 break; 1143 } 1144 } 1145 } 1146 1147 if ($value['value'] === null && $global_macros[$macro]['value'] !== null) { 1148 if ($context === null) { 1149 $value['value'] = $global_macros[$macro]['value']; 1150 } 1151 elseif ($value['value_default'] === null) { 1152 $value['value_default'] = $global_macros[$macro]['value']; 1153 } 1154 } 1155 } 1156 } 1157 } 1158 unset($value); 1159 } 1160 unset($element); 1161 } 1162 1163 foreach ($data as $key => $element) { 1164 foreach ($element['macros'] as $usermacro => $value) { 1165 if ($value['value'] !== null) { 1166 $data[$key]['macros'][$usermacro] = $value['value']; 1167 } 1168 elseif ($value['value_default'] !== null) { 1169 $data[$key]['macros'][$usermacro] = $value['value_default']; 1170 } 1171 // Unresolved macro. 1172 elseif ($unset_undefined) { 1173 unset($data[$key]['macros'][$usermacro]); 1174 } 1175 else { 1176 $data[$key]['macros'][$usermacro] = $usermacro; 1177 } 1178 } 1179 } 1180 1181 return $data; 1182 } 1183 1184 /** 1185 * Get user macro from the requested hosts. 1186 * 1187 * Use the base value returned by host macro as default value when expanding expand global macro. This will ensure 1188 * the following user macro resolving priority: 1189 * 1) host/template context macro 1190 * 2) global context macro 1191 * 3) host/template base (default) macro 1192 * 4) global base (default) macro 1193 * 1194 * @param array $hostids The sorted list of hosts where macros will be looked for (hostid => hostid) 1195 * @param string $macro Macro base without curly braces, for example: SNMP_COMMUNITY 1196 * @param string $context Macro context to resolve 1197 * @param array $host_templates The list of linked templates (see getUserMacros() for more details) 1198 * @param array $host_macros The list of macros on hosts (see getUserMacros() for more details) 1199 * @param string $value_default Value 1200 * 1201 * @return array 1202 */ 1203 private function getHostUserMacros(array $hostids, $macro, $context, array $host_templates, array $host_macros, 1204 $value_default = null) { 1205 foreach ($hostids as $hostid) { 1206 if (array_key_exists($hostid, $host_macros) && array_key_exists($macro, $host_macros[$hostid])) { 1207 // Searching context coincidence with macro contexts. 1208 if ($context !== null && array_key_exists($context, $host_macros[$hostid][$macro]['contexts'])) { 1209 return [ 1210 'value' => $host_macros[$hostid][$macro]['contexts'][$context], 1211 'value_default' => $value_default 1212 ]; 1213 } 1214 // Searching context coincidence, if regex array not empty. 1215 elseif ($context !== null && count($host_macros[$hostid][$macro]['regex'])) { 1216 foreach ($host_macros[$hostid][$macro]['regex'] as $regex => $val) { 1217 if (preg_match('/'.strtr(trim($regex, '/'), ['/' => '\\/']).'/', $context) === 1) { 1218 return [ 1219 'value' => $val, 1220 'value_default' => $value_default 1221 ]; 1222 } 1223 } 1224 } 1225 1226 if ($host_macros[$hostid][$macro]['value'] !== null) { 1227 if ($context === null) { 1228 return ['value' => $host_macros[$hostid][$macro]['value'], 'value_default' => $value_default]; 1229 } 1230 elseif ($value_default === null) { 1231 $value_default = $host_macros[$hostid][$macro]['value']; 1232 } 1233 } 1234 } 1235 } 1236 1237 if (!$host_templates) { 1238 return ['value' => null, 'value_default' => $value_default]; 1239 } 1240 1241 $templateids = []; 1242 1243 foreach ($hostids as $hostid) { 1244 if (array_key_exists($hostid, $host_templates)) { 1245 foreach ($host_templates[$hostid] as $templateid) { 1246 $templateids[$templateid] = true; 1247 } 1248 } 1249 } 1250 1251 if ($templateids) { 1252 $templateids = array_keys($templateids); 1253 natsort($templateids); 1254 1255 return $this->getHostUserMacros($templateids, $macro, $context, $host_templates, $host_macros, 1256 $value_default 1257 ); 1258 } 1259 1260 return ['value' => null, 'value_default' => $value_default]; 1261 } 1262 1263 /** 1264 * Get macro value refer by type. 1265 * 1266 * @static 1267 * 1268 * @param array $macro 1269 * 1270 * @return string 1271 */ 1272 public static function getMacroValue(array $macro): string { 1273 return ($macro['type'] == ZBX_MACRO_TYPE_SECRET) ? ZBX_SECRET_MASK : $macro['value']; 1274 } 1275 1276 /** 1277 * Sorting host macros. 1278 * 1279 * @param array $host_macros 1280 * 1281 * @return array 1282 */ 1283 private static function sortRegexHostMacros(array $host_macros): array { 1284 foreach ($host_macros as &$macros) { 1285 foreach ($macros as &$value) { 1286 $value['regex'] = self::sortRegex($value['regex']); 1287 } 1288 unset($value); 1289 } 1290 unset($macros); 1291 1292 return $host_macros; 1293 } 1294 1295 /** 1296 * Sorting global macros. 1297 * 1298 * @static 1299 * 1300 * @param array $global_macros 1301 * 1302 * @return array 1303 */ 1304 private static function sortRegexGlobalMacros(array $global_macros): array { 1305 foreach ($global_macros as &$value) { 1306 $value['regex'] = self::sortRegex($value['regex']); 1307 } 1308 unset($value); 1309 1310 return $global_macros; 1311 } 1312 1313 /** 1314 * Sort regex. 1315 * 1316 * @static 1317 * 1318 * @param array $macros 1319 * 1320 * @return array 1321 */ 1322 private static function sortRegex(array $macros): array { 1323 $keys = array_keys($macros); 1324 1325 usort($keys, 'strcmp'); 1326 1327 $new_array = []; 1328 1329 foreach($keys as $key) { 1330 $new_array[$key] = $macros[$key]; 1331 } 1332 1333 return $new_array; 1334 } 1335} 1336