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