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