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; ifnot, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22function getSeverityStyle($severity, $type = true) { 23 if (!$type) { 24 return ZBX_STYLE_NORMAL_BG; 25 } 26 27 switch ($severity) { 28 case TRIGGER_SEVERITY_DISASTER: 29 return ZBX_STYLE_DISASTER_BG; 30 case TRIGGER_SEVERITY_HIGH: 31 return ZBX_STYLE_HIGH_BG; 32 case TRIGGER_SEVERITY_AVERAGE: 33 return ZBX_STYLE_AVERAGE_BG; 34 case TRIGGER_SEVERITY_WARNING: 35 return ZBX_STYLE_WARNING_BG; 36 case TRIGGER_SEVERITY_INFORMATION: 37 return ZBX_STYLE_INFO_BG; 38 case TRIGGER_SEVERITY_NOT_CLASSIFIED: 39 return ZBX_STYLE_NA_BG; 40 default: 41 return null; 42 } 43} 44 45/** 46 * Get trigger severity name by given state and configuration. 47 * 48 * @param int $severity trigger severity 49 * @param array $config array containing configuration parameters containing severity names 50 * 51 * @return string 52 */ 53function getSeverityName($severity, array $config) { 54 switch ($severity) { 55 case TRIGGER_SEVERITY_NOT_CLASSIFIED: 56 return _($config['severity_name_0']); 57 case TRIGGER_SEVERITY_INFORMATION: 58 return _($config['severity_name_1']); 59 case TRIGGER_SEVERITY_WARNING: 60 return _($config['severity_name_2']); 61 case TRIGGER_SEVERITY_AVERAGE: 62 return _($config['severity_name_3']); 63 case TRIGGER_SEVERITY_HIGH: 64 return _($config['severity_name_4']); 65 case TRIGGER_SEVERITY_DISASTER: 66 return _($config['severity_name_5']); 67 default: 68 return _('Unknown'); 69 } 70} 71 72function getSeverityColor($severity, $value = TRIGGER_VALUE_TRUE) { 73 if ($value == TRIGGER_VALUE_FALSE) { 74 return 'AAFFAA'; 75 } 76 $config = select_config(); 77 78 switch ($severity) { 79 case TRIGGER_SEVERITY_DISASTER: 80 $color = $config['severity_color_5']; 81 break; 82 case TRIGGER_SEVERITY_HIGH: 83 $color = $config['severity_color_4']; 84 break; 85 case TRIGGER_SEVERITY_AVERAGE: 86 $color = $config['severity_color_3']; 87 break; 88 case TRIGGER_SEVERITY_WARNING: 89 $color = $config['severity_color_2']; 90 break; 91 case TRIGGER_SEVERITY_INFORMATION: 92 $color = $config['severity_color_1']; 93 break; 94 case TRIGGER_SEVERITY_NOT_CLASSIFIED: 95 $color = $config['severity_color_0']; 96 break; 97 default: 98 $color = $config['severity_color_0']; 99 } 100 101 return $color; 102} 103 104/** 105 * Returns HTML representation of trigger severity cell containing severity name and color. 106 * 107 * @param int $severity trigger severity 108 * @param array $config array of configuration parameters to get trigger severity name 109 * @param string $text trigger severity name 110 * @param bool $forceNormal true to return 'normal' class, false to return corresponding severity class 111 * 112 * @return CCol 113 */ 114function getSeverityCell($severity, $config, $text = null, $forceNormal = false) { 115 if ($text === null) { 116 $text = CHtml::encode(getSeverityName($severity, $config)); 117 } 118 119 return (new CCol($text))->addClass(getSeverityStyle($severity, !$forceNormal)); 120} 121 122/** 123 * Add color style and blinking to an object like CSpan or CDiv depending on trigger status 124 * Settings and colors are kept in 'config' database table 125 * 126 * @param mixed $object object like CSpan, CDiv, etc. 127 * @param int $triggerValue TRIGGER_VALUE_FALSE or TRIGGER_VALUE_TRUE 128 * @param int $triggerLastChange 129 * @param bool $isAcknowledged 130 * @return void 131 */ 132function addTriggerValueStyle($object, $triggerValue, $triggerLastChange, $isAcknowledged) { 133 $config = select_config(); 134 135 // color of text and blinking depends on trigger value and whether event is acknowledged 136 if ($triggerValue == TRIGGER_VALUE_TRUE && !$isAcknowledged) { 137 $color = $config['problem_unack_color']; 138 $blinks = $config['problem_unack_style']; 139 } 140 elseif ($triggerValue == TRIGGER_VALUE_TRUE && $isAcknowledged) { 141 $color = $config['problem_ack_color']; 142 $blinks = $config['problem_ack_style']; 143 } 144 elseif ($triggerValue == TRIGGER_VALUE_FALSE && !$isAcknowledged) { 145 $color = $config['ok_unack_color']; 146 $blinks = $config['ok_unack_style']; 147 } 148 elseif ($triggerValue == TRIGGER_VALUE_FALSE && $isAcknowledged) { 149 $color = $config['ok_ack_color']; 150 $blinks = $config['ok_ack_style']; 151 } 152 if (isset($color) && isset($blinks)) { 153 // color 154 $object->addStyle('color: #'.$color); 155 156 // blinking 157 $timeSinceLastChange = time() - $triggerLastChange; 158 if ($blinks && $timeSinceLastChange < $config['blink_period']) { 159 $object->addClass('blink'); // elements with this class will blink 160 $object->setAttribute('data-time-to-blink', $config['blink_period'] - $timeSinceLastChange); 161 } 162 } 163 else { 164 $object->addClass(ZBX_STYLE_GREY); 165 } 166} 167 168function trigger_value2str($value = null) { 169 $triggerValues = [ 170 TRIGGER_VALUE_FALSE => _('OK'), 171 TRIGGER_VALUE_TRUE => _('PROBLEM') 172 ]; 173 174 if ($value === null) { 175 return $triggerValues; 176 } 177 elseif (isset($triggerValues[$value])) { 178 return $triggerValues[$value]; 179 } 180 else { 181 return _('Unknown'); 182 } 183} 184 185function discovery_value($val = null) { 186 $array = [ 187 DOBJECT_STATUS_UP => _('UP'), 188 DOBJECT_STATUS_DOWN => _('DOWN'), 189 DOBJECT_STATUS_DISCOVER => _('DISCOVERED'), 190 DOBJECT_STATUS_LOST => _('LOST') 191 ]; 192 193 if (is_null($val)) { 194 return $array; 195 } 196 elseif (isset($array[$val])) { 197 return $array[$val]; 198 } 199 else { 200 return _('Unknown'); 201 } 202} 203 204function discovery_value_style($val) { 205 switch ($val) { 206 case DOBJECT_STATUS_UP: 207 $style = ZBX_STYLE_GREEN; 208 break; 209 case DOBJECT_STATUS_DOWN: 210 $style = ZBX_STYLE_RED; 211 break; 212 case DOBJECT_STATUS_DISCOVER: 213 $style = ZBX_STYLE_GREEN; 214 break; 215 case DOBJECT_STATUS_LOST: 216 $style = ZBX_STYLE_GREY; 217 break; 218 default: 219 $style = ''; 220 } 221 222 return $style; 223} 224 225function getParentHostsByTriggers($triggers) { 226 $hosts = []; 227 $triggerParent = []; 228 229 while (!empty($triggers)) { 230 foreach ($triggers as $tnum => $trigger) { 231 if ($trigger['templateid'] == 0) { 232 if (isset($triggerParent[$trigger['triggerid']])) { 233 foreach ($triggerParent[$trigger['triggerid']] as $triggerid => $state) { 234 $hosts[$triggerid] = $trigger['hosts']; 235 } 236 } 237 else { 238 $hosts[$trigger['triggerid']] = $trigger['hosts']; 239 } 240 unset($triggers[$tnum]); 241 } 242 else { 243 if (isset($triggerParent[$trigger['triggerid']])) { 244 if (!isset($triggerParent[$trigger['templateid']])) { 245 $triggerParent[$trigger['templateid']] = []; 246 } 247 $triggerParent[$trigger['templateid']][$trigger['triggerid']] = 1; 248 $triggerParent[$trigger['templateid']] += $triggerParent[$trigger['triggerid']]; 249 } 250 else { 251 if (!isset($triggerParent[$trigger['templateid']])) { 252 $triggerParent[$trigger['templateid']] = []; 253 } 254 $triggerParent[$trigger['templateid']][$trigger['triggerid']] = 1; 255 } 256 } 257 } 258 $triggers = API::Trigger()->get([ 259 'triggerids' => zbx_objectValues($triggers, 'templateid'), 260 'selectHosts' => ['hostid', 'host', 'name', 'status'], 261 'output' => ['triggerid', 'templateid'], 262 'filter' => ['flags' => null] 263 ]); 264 } 265 266 return $hosts; 267} 268 269function get_trigger_by_triggerid($triggerid) { 270 $db_trigger = DBfetch(DBselect('SELECT t.* FROM triggers t WHERE t.triggerid='.zbx_dbstr($triggerid))); 271 if (!empty($db_trigger)) { 272 return $db_trigger; 273 } 274 error(_s('No trigger with triggerid "%1$s".', $triggerid)); 275 276 return false; 277} 278 279function get_hosts_by_triggerid($triggerids) { 280 zbx_value2array($triggerids); 281 282 return DBselect( 283 'SELECT DISTINCT h.*'. 284 ' FROM hosts h,functions f,items i'. 285 ' WHERE i.itemid=f.itemid'. 286 ' AND h.hostid=i.hostid'. 287 ' AND '.dbConditionInt('f.triggerid', $triggerids) 288 ); 289} 290 291function get_triggers_by_hostid($hostid) { 292 return DBselect( 293 'SELECT DISTINCT t.*'. 294 ' FROM triggers t,functions f,items i'. 295 ' WHERE i.hostid='.zbx_dbstr($hostid). 296 ' AND f.itemid=i.itemid'. 297 ' AND f.triggerid=t.triggerid' 298 ); 299} 300 301// unescape Raw URL 302function utf8RawUrlDecode($source) { 303 $decodedStr = ''; 304 $pos = 0; 305 $len = strlen($source); 306 while ($pos < $len) { 307 $charAt = substr($source, $pos, 1); 308 if ($charAt == '%') { 309 $pos++; 310 $charAt = substr($source, $pos, 1); 311 if ($charAt == 'u') { 312 // we got a unicode character 313 $pos++; 314 $unicodeHexVal = substr($source, $pos, 4); 315 $unicode = hexdec($unicodeHexVal); 316 $entity = "&#".$unicode.';'; 317 $decodedStr .= html_entity_decode(utf8_encode($entity), ENT_COMPAT, 'UTF-8'); 318 $pos += 4; 319 } 320 else { 321 $decodedStr .= substr($source, $pos-1, 1); 322 } 323 } 324 else { 325 $decodedStr .= $charAt; 326 $pos++; 327 } 328 } 329 330 return $decodedStr; 331} 332 333/** 334 * Copies the given triggers to the given hosts or templates. 335 * 336 * Without the $srcHostId parameter it will only be able to copy triggers that belong to only one host. If the 337 * $srcHostId parameter is not passed, and a trigger has multiple hosts, it will throw an error. If the 338 * $srcHostId parameter is passed, the given host will be replaced with the destination host. 339 * 340 * This function takes care of copied trigger dependencies. 341 * If trigger is copied alongside with trigger on which it depends, then dependencies is replaced directly using new ids, 342 * If there is target host within dependency trigger, algorithm will search for potential matching trigger in target host, 343 * if matching trigger is found, then id from this trigger is used, if not rise exception, 344 * otherwise original dependency will be left. 345 * 346 * 347 * @param int|array $srcTriggerIds triggers which will be copied to $dstHostIds 348 * @param int|array $dstHostIds hosts and templates to whom add triggers, ids not present in DB (host table) will be ignored 349 * @param int $srcHostId host id in which context trigger with multiple hosts will be treated 350 * 351 * @return bool 352 */ 353function copyTriggersToHosts($srcTriggerIds, $dstHostIds, $srcHostId = null) { 354 $options = [ 355 'triggerids' => $srcTriggerIds, 356 'output' => ['triggerid', 'expression', 'description', 'url', 'status', 'priority', 'comments', 'type'], 357 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL], 358 'selectDependencies' => ['triggerid'] 359 ]; 360 if ($srcHostId) { 361 $srcHost = API::Host()->get([ 362 'output' => ['host'], 363 'hostids' => $srcHostId, 364 'preservekeys' => true, 365 'templated_hosts' => true 366 ]); 367 368 // If provided $srcHostId doesn't match any record in DB, return false. 369 if (!($srcHost = reset($srcHost))) { 370 return false; 371 } 372 } 373 // If no $srcHostId provided we will need trigger host 'host'. 374 else { 375 $options['selectHosts'] = ['host']; 376 } 377 $dbSrcTriggers = API::Trigger()->get($options); 378 379 $dbSrcTriggers = CMacrosResolverHelper::resolveTriggerExpressions($dbSrcTriggers); 380 381 $dbDstHosts = API::Host()->get([ 382 'output' => ['hostid', 'host'], 383 'hostids' => $dstHostIds, 384 'preservekeys' => true, 385 'templated_hosts' => true 386 ]); 387 388 $newTriggers = []; 389 390 // Create each trigger for each host. 391 foreach ($dbDstHosts as $dstHost) { 392 foreach ($dbSrcTriggers as $srcTrigger) { 393 // If $srcHostId provided, get host 'host' for triggerExpressionReplaceHost(). 394 if ($srcHostId != 0) { 395 $host = $srcHost['host']; 396 $srcTriggerContextHostId = $srcHostId; 397 } 398 // If $srcHostId not provided, use source trigger first host 'host'. 399 else { 400 401 /* 402 * If we have multiple hosts in trigger expression and we haven't pointed ($srcHostId) which host to 403 * replace, call error. 404 */ 405 if (count($srcTrigger['hosts']) > 1) { 406 error(_s('Cannot copy trigger "%1$s:%2$s", because it has multiple hosts in the expression.', 407 $srcTrigger['description'], $srcTrigger['expression'] 408 )); 409 410 return false; 411 } 412 $host = $srcTrigger['hosts'][0]['host']; 413 $srcTriggerContextHostId = $srcTrigger['hosts'][0]['hostid']; 414 } 415 416 $srcTrigger['expression'] = triggerExpressionReplaceHost($srcTrigger['expression'], $host, 417 $dstHost['host'] 418 ); 419 420 // The dependddencies must be added after all triggers are created. 421 $result = API::Trigger()->create([[ 422 'description' => $srcTrigger['description'], 423 'expression' => $srcTrigger['expression'], 424 'url' => $srcTrigger['url'], 425 'status' => $srcTrigger['status'], 426 'priority' => $srcTrigger['priority'], 427 'comments' => $srcTrigger['comments'], 428 'type' => $srcTrigger['type'] 429 ]]); 430 431 if (!$result) { 432 return false; 433 } 434 435 $newTriggers[$srcTrigger['triggerid']][] = [ 436 'newTriggerId' => reset($result['triggerids']), 437 'newTriggerExpression' => $srcTrigger['expression'], 438 'newTriggerHostId' => $dstHost['hostid'], 439 'newTriggerHost' => $dstHost['host'], 440 'srcTriggerContextHostId' => $srcTriggerContextHostId, 441 'srcTriggerContextHost' => $host 442 ]; 443 } 444 } 445 446 $depids = []; 447 foreach ($dbSrcTriggers as $srcTrigger) { 448 foreach ($srcTrigger['dependencies'] as $depTrigger) { 449 $depids[] = $depTrigger['triggerid']; 450 } 451 } 452 $depTriggers = API::Trigger()->get([ 453 'triggerids' => $depids, 454 'output' => ['description', 'expression'], 455 'selectHosts' => ['hostid'], 456 'preservekeys' => true 457 ]); 458 459 $depTriggers = CMacrosResolverHelper::resolveTriggerExpressions($depTriggers); 460 461 // Map dependencies to the new trigger IDs and save. 462 if ($newTriggers) { 463 $dependencies = []; 464 465 foreach ($dbSrcTriggers as $srcTrigger) { 466 if ($srcTrigger['dependencies']) { 467 // Get corresponding created triggers. 468 $dst_triggers = $newTriggers[$srcTrigger['triggerid']]; 469 470 foreach ($dst_triggers as $dst_trigger) { 471 foreach ($srcTrigger['dependencies'] as $depTrigger) { 472 /* 473 * We have added $depTrigger trigger and we know corresponding trigger ID for newly 474 * created trigger. 475 */ 476 if (array_key_exists($depTrigger['triggerid'], $newTriggers)) { 477 $dst_dep_triggers = $newTriggers[$depTrigger['triggerid']]; 478 479 foreach ($dst_dep_triggers as $dst_dep_trigger) { 480 /* 481 * Dependency is within same host according to $srcHostId parameter or dep trigger has 482 * single host. 483 */ 484 if ($dst_trigger['srcTriggerContextHostId'] == $dst_dep_trigger['srcTriggerContextHostId'] 485 && $dst_dep_trigger['newTriggerHostId'] == $dst_trigger['newTriggerHostId']) { 486 $depTriggerId = $dst_dep_trigger['newTriggerId']; 487 break; 488 } 489 // Dependency is to trigger from another host. 490 else { 491 $depTriggerId = $depTrigger['triggerid']; 492 } 493 } 494 } 495 // We need to search for $depTrigger trigger if target host is within dependency hosts. 496 elseif (in_array(['hostid' => $dst_trigger['srcTriggerContextHostId']], 497 $depTriggers[$depTrigger['triggerid']]['hosts'])) { 498 // Get all possible $depTrigger matching triggers by description. 499 $targetHostTriggersByDescription = API::Trigger()->get([ 500 'hostids' => $dst_trigger['newTriggerHostId'], 501 'output' => ['hosts', 'triggerid', 'expression'], 502 'filter' => ['description' => $depTriggers[$depTrigger['triggerid']]['description']], 503 'preservekeys' => true 504 ]); 505 506 $targetHostTriggersByDescription = 507 CMacrosResolverHelper::resolveTriggerExpressions($targetHostTriggersByDescription); 508 509 // Compare exploded expressions for exact match. 510 $expr1 = $depTriggers[$depTrigger['triggerid']]['expression']; 511 $depTriggerId = null; 512 513 foreach ($targetHostTriggersByDescription as $potentialTargetTrigger) { 514 $expr2 = triggerExpressionReplaceHost($potentialTargetTrigger['expression'], 515 $dst_trigger['newTriggerHost'], $dst_trigger['srcTriggerContextHost'] 516 ); 517 518 if ($expr2 == $expr1) { 519 // Matching trigger has been found. 520 $depTriggerId = $potentialTargetTrigger['triggerid']; 521 break; 522 } 523 } 524 525 // If matching trigger wasn't found raise exception. 526 if ($depTriggerId === null) { 527 $expr2 = triggerExpressionReplaceHost($expr1, $dst_trigger['srcTriggerContextHost'], 528 $dst_trigger['newTriggerHost'] 529 ); 530 531 error(_s( 532 'Cannot add dependency from trigger "%1$s:%2$s" to non existing trigger "%3$s:%4$s".', 533 $srcTrigger['description'], $dst_trigger['newTriggerExpression'], 534 $depTriggers[$depTrigger['triggerid']]['description'], $expr2 535 )); 536 537 return false; 538 } 539 } 540 // Leave original dependency. 541 else { 542 $depTriggerId = $depTrigger['triggerid']; 543 } 544 545 $dependencies[] = [ 546 'triggerid' => $dst_trigger['newTriggerId'], 547 'dependsOnTriggerid' => $depTriggerId 548 ]; 549 } 550 } 551 } 552 } 553 554 if ($dependencies) { 555 if (!API::Trigger()->addDependencies($dependencies)) { 556 return false; 557 } 558 } 559 } 560 561 return true; 562} 563 564/** 565 * Purpose: Replaces host in trigger expression. 566 * {localhost:agent.ping.nodata(5m)} => {localhost6:agent.ping.nodata(5m)} 567 * 568 * @param string $expression full expression with host names and item keys 569 * @param string $src_host 570 * @param string $dst_host 571 * 572 * @return string 573 */ 574function triggerExpressionReplaceHost($expression, $src_host, $dst_host) { 575 $new_expression = ''; 576 577 $function_macro_parser = new CFunctionMacroParser(); 578 $user_macro_parser = new CUserMacroParser(); 579 $macro_parser = new CMacroParser(['{TRIGGER.VALUE}']); 580 $lld_macro_parser = new CLLDMacroParser(); 581 582 for ($pos = 0, $pos_left = 0; isset($expression[$pos]); $pos++) { 583 if ($function_macro_parser->parse($expression, $pos) != CParser::PARSE_FAIL) { 584 $host = $function_macro_parser->getHost(); 585 $item = $function_macro_parser->getItem(); 586 $function = $function_macro_parser->getFunction(); 587 588 if ($host === $src_host) { 589 $host = $dst_host; 590 } 591 592 $new_expression .= substr($expression, $pos_left, $pos - $pos_left); 593 $new_expression .= '{'.$host.':'.$item.'.'.$function.'}'; 594 $pos_left = $pos + $function_macro_parser->getLength(); 595 596 $pos += $function_macro_parser->getLength() - 1; 597 } 598 elseif ($user_macro_parser->parse($expression, $pos) != CParser::PARSE_FAIL) { 599 $pos += $user_macro_parser->getLength() - 1; 600 } 601 elseif ($macro_parser->parse($expression, $pos) != CParser::PARSE_FAIL) { 602 $pos += $macro_parser->getLength() - 1; 603 } 604 elseif ($lld_macro_parser->parse($expression, $pos) != CParser::PARSE_FAIL) { 605 $pos += $lld_macro_parser->getLength() - 1; 606 } 607 } 608 609 $new_expression .= substr($expression, $pos_left, $pos - $pos_left); 610 611 return $new_expression; 612} 613 614/** 615 * Implodes expression, replaces names and keys with IDs. 616 * 617 * For example: localhost:system.cpu.load.last(0)>10 will be translated to {12}>10 and created database representation. 618 * 619 * @throws Exception if error occurred 620 * 621 * @param string $expression Full expression with host names and item keys 622 * @param numeric $triggerid 623 * @param array optional $hostnames Reference to array which will be filled with unique visible host names. 624 * 625 * @return string Imploded expression (names and keys replaced by IDs) 626 */ 627function implode_exp($expression, $triggerId, &$hostnames = []) { 628 $expressionData = new CTriggerExpression(); 629 if (!$expressionData->parse($expression)) { 630 throw new Exception($expressionData->error); 631 } 632 633 $newFunctions = []; 634 $functions = []; 635 $items = []; 636 $triggerFunctionValidator = new CFunctionValidator(); 637 638 foreach ($expressionData->expressions as $exprPart) { 639 if (isset($newFunctions[$exprPart['expression']])) { 640 continue; 641 } 642 643 if (!isset($items[$exprPart['host']][$exprPart['item']])) { 644 $result = DBselect( 645 'SELECT i.itemid,i.value_type,h.name'. 646 ' FROM items i,hosts h'. 647 ' WHERE i.key_='.zbx_dbstr($exprPart['item']). 648 ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED, ZBX_FLAG_DISCOVERY_PROTOTYPE]). 649 ' AND h.host='.zbx_dbstr($exprPart['host']). 650 ' AND h.hostid=i.hostid' 651 ); 652 if ($row = DBfetch($result)) { 653 $hostnames[] = $row['name']; 654 $items[$exprPart['host']][$exprPart['item']] = [ 655 'itemid' => $row['itemid'], 656 'valueType' => $row['value_type'] 657 ]; 658 } 659 else { 660 throw new Exception(_s('Incorrect item key "%1$s" provided for trigger expression on "%2$s".', 661 $exprPart['item'], $exprPart['host'])); 662 } 663 } 664 665 if (!$triggerFunctionValidator->validate([ 666 'function' => $exprPart['function'], 667 'functionName' => $exprPart['functionName'], 668 'functionParamList' => $exprPart['functionParamList'], 669 'valueType' => $items[$exprPart['host']][$exprPart['item']]['valueType']])) { 670 throw new Exception($triggerFunctionValidator->getError()); 671 } 672 673 $newFunctions[$exprPart['expression']] = 0; 674 675 $functions[] = [ 676 'itemid' => $items[$exprPart['host']][$exprPart['item']]['itemid'], 677 'triggerid' => $triggerId, 678 'function' => $exprPart['functionName'], 679 'parameter' => $exprPart['functionParam'] 680 ]; 681 } 682 683 $functionIds = DB::insert('functions', $functions); 684 685 $num = 0; 686 foreach ($newFunctions as &$newFunction) { 687 $newFunction = $functionIds[$num++]; 688 } 689 unset($newFunction); 690 691 $exprPart = end($expressionData->expressions); 692 do { 693 $expression = substr_replace($expression, '{'.$newFunctions[$exprPart['expression']].'}', 694 $exprPart['pos'], strlen($exprPart['expression'])); 695 } 696 while ($exprPart = prev($expressionData->expressions)); 697 698 $hostnames = array_unique($hostnames); 699 700 return $expression; 701} 702 703function check_right_on_trigger_by_expression($permission, $expression) { 704 $expressionData = new CTriggerExpression(); 705 if (!$expressionData->parse($expression)) { 706 error($expressionData->error); 707 return false; 708 } 709 $expressionHosts = $expressionData->getHosts(); 710 711 $hosts = API::Host()->get([ 712 'filter' => ['host' => $expressionHosts], 713 'editable' => ($permission == PERM_READ_WRITE), 714 'output' => ['hostid', 'host'], 715 'templated_hosts' => true, 716 'preservekeys' => true 717 ]); 718 $hosts = zbx_toHash($hosts, 'host'); 719 720 foreach ($expressionHosts as $host) { 721 if (!isset($hosts[$host])) { 722 error(_s('Incorrect trigger expression. Host "%1$s" does not exist or you have no access to this host.', $host)); 723 return false; 724 } 725 } 726 727 return true; 728} 729 730function replace_template_dependencies($deps, $hostid) { 731 foreach ($deps as $id => $val) { 732 $sql = 'SELECT t.triggerid'. 733 ' FROM triggers t,functions f,items i'. 734 ' WHERE t.triggerid=f.triggerid'. 735 ' AND f.itemid=i.itemid'. 736 ' AND t.templateid='.zbx_dbstr($val). 737 ' AND i.hostid='.zbx_dbstr($hostid); 738 if ($db_new_dep = DBfetch(DBselect($sql))) { 739 $deps[$id] = $db_new_dep['triggerid']; 740 } 741 } 742 743 return $deps; 744} 745 746/** 747 * Creates and returns the trigger overview table for the given hosts. 748 * 749 * @param array $hosts an array of hosts with host IDs as keys 750 * @param string $hosts[hostid][name] 751 * @param string $hosts[hostid][hostid] 752 * @param array $triggers 753 * @param string $triggers[][triggerid] 754 * @param string $triggers[][description] 755 * @param string $triggers[][expression] 756 * @param int $triggers[][value] 757 * @param int $triggers[][lastchange] 758 * @param int $triggers[][flags] 759 * @param array $triggers[][url] 760 * @param int $triggers[][priority] 761 * @param array $triggers[][hosts] 762 * @param string $triggers[][hosts][][hostid] 763 * @param string $triggers[][hosts][][name] 764 * @param string $pageFile the page where the element is displayed 765 * @param int $viewMode table display style: either hosts on top, or host on the left side 766 * @param string $screenId the ID of the screen, that contains the trigger overview table 767 * 768 * @return CTableInfo 769 */ 770function getTriggersOverview(array $hosts, array $triggers, $pageFile, $viewMode = null, $screenId = null) { 771 $data = []; 772 $hostNames = []; 773 $trcounter = []; 774 775 $triggers = CMacrosResolverHelper::resolveTriggerNames($triggers, true); 776 777 foreach ($triggers as $trigger) { 778 $trigger_name = $trigger['description']; 779 780 foreach ($trigger['hosts'] as $host) { 781 // triggers may belong to hosts that are filtered out and shouldn't be displayed, skip them 782 if (!isset($hosts[$host['hostid']])) { 783 continue; 784 } 785 786 $hostNames[$host['hostid']] = $host['name']; 787 788 if (!array_key_exists($host['name'], $trcounter)) { 789 $trcounter[$host['name']] = []; 790 } 791 792 if (!array_key_exists($trigger_name, $trcounter[$host['name']])) { 793 $trcounter[$host['name']][$trigger_name] = 0; 794 } 795 796 $data[$trigger_name][$trcounter[$host['name']][$trigger_name]][$host['name']] = [ 797 'groupid' => $trigger['groupid'], 798 'hostid' => $host['hostid'], 799 'triggerid' => $trigger['triggerid'], 800 'value' => $trigger['value'], 801 'lastchange' => $trigger['lastchange'], 802 'priority' => $trigger['priority'], 803 'flags' => $trigger['flags'], 804 'url' => $trigger['url'], 805 'hosts' => $trigger['hosts'], 806 'items' => $trigger['items'] 807 ]; 808 $trcounter[$host['name']][$trigger_name]++; 809 } 810 } 811 812 $triggerTable = new CTableInfo(); 813 814 if (empty($hostNames)) { 815 return $triggerTable; 816 } 817 818 $triggerTable->makeVerticalRotation(); 819 820 order_result($hostNames); 821 822 if ($viewMode == STYLE_TOP) { 823 // header 824 $header = [_('Triggers')]; 825 826 foreach ($hostNames as $hostName) { 827 $header[] = (new CColHeader($hostName))->addClass('vertical_rotation'); 828 } 829 $triggerTable->setHeader($header); 830 831 // data 832 foreach ($data as $trigger_name => $trigger_data) { 833 foreach ($trigger_data as $trigger_hosts) { 834 $columns = [nbsp($trigger_name)]; 835 836 foreach ($hostNames as $hostName) { 837 $columns[] = getTriggerOverviewCells( 838 isset($trigger_hosts[$hostName]) ? $trigger_hosts[$hostName] : null, 839 $pageFile, 840 $screenId 841 ); 842 } 843 $triggerTable->addRow($columns); 844 } 845 } 846 } 847 else { 848 // header 849 $header = [_('Host')]; 850 851 foreach ($data as $trigger_name => $trigger_data) { 852 foreach ($trigger_data as $trigger_hosts) { 853 $header[] = (new CColHeader($trigger_name))->addClass('vertical_rotation'); 854 } 855 } 856 857 $triggerTable->setHeader($header); 858 859 // data 860 $scripts = API::Script()->getScriptsByHosts(zbx_objectValues($hosts, 'hostid')); 861 862 foreach ($hostNames as $hostId => $hostName) { 863 $name = (new CSpan($hostName))->addClass(ZBX_STYLE_LINK_ACTION); 864 $name->setMenuPopup(CMenuPopupHelper::getHost($hosts[$hostId], $scripts[$hostId])); 865 866 $columns = [(new CCol($name))->addClass(ZBX_STYLE_NOWRAP)]; 867 foreach ($data as $trigger_data) { 868 foreach ($trigger_data as $trigger_hosts) { 869 $columns[] = getTriggerOverviewCells( 870 isset($trigger_hosts[$hostName]) ? $trigger_hosts[$hostName] : null, 871 $pageFile, 872 $screenId 873 ); 874 } 875 } 876 877 $triggerTable->addRow($columns); 878 } 879 } 880 881 return $triggerTable; 882} 883 884/** 885 * Creates and returns a trigger status cell for the trigger overview table. 886 * 887 * @see getTriggersOverview() 888 * 889 * @param array $trigger 890 * @param string $pageFile the page where the element is displayed 891 * @param string $screenid 892 * 893 * @return CCol 894 */ 895function getTriggerOverviewCells($trigger, $pageFile, $screenid = null) { 896 $ack = null; 897 $css = null; 898 $desc = []; 899 $acknowledge = []; 900 901 // for how long triggers should blink on status change (set by user in administration->general) 902 $config = select_config(); 903 904 if ($trigger) { 905 $css = getSeverityStyle($trigger['priority'], $trigger['value'] == TRIGGER_VALUE_TRUE); 906 907 // problem trigger 908 if ($trigger['value'] == TRIGGER_VALUE_TRUE) { 909 $ack = null; 910 911 if ($config['event_ack_enable']) { 912 if ($event = get_last_event_by_triggerid($trigger['triggerid'])) { 913 if ($screenid !== null) { 914 $acknowledge = [ 915 'eventid' => $event['eventid'], 916 'backurl' => $pageFile.'?screenid='.$screenid 917 ]; 918 } 919 else { 920 $acknowledge = [ 921 'eventid' => $event['eventid'], 922 'backurl' => $pageFile 923 ]; 924 } 925 926 if ($event['acknowledged'] == 1) { 927 $ack = (new CSpan())->addClass(ZBX_STYLE_ICON_ACKN); 928 } 929 } 930 } 931 } 932 933 // dependency: triggers on which depends this 934 $triggerId = empty($trigger['triggerid']) ? 0 : $trigger['triggerid']; 935 936 // trigger dependency DOWN 937 $dependencyTable = (new CTableInfo()) 938 ->setAttribute('style', 'width: 200px;') 939 ->addRow(bold(_('Depends on').':')); 940 941 $isDependencyFound = false; 942 $dbDependencies = DBselect('SELECT td.* FROM trigger_depends td WHERE td.triggerid_down='.zbx_dbstr($triggerId)); 943 while ($dbDependency = DBfetch($dbDependencies)) { 944 $dependencyTable->addRow(SPACE.'-'.SPACE.CMacrosResolverHelper::resolveTriggerNameById($dbDependency['triggerid_up'])); 945 $isDependencyFound = true; 946 } 947 948 if ($isDependencyFound) { 949 $desc[] = (new CSpan()) 950 ->addClass(ZBX_STYLE_ICON_DEPEND_DOWN) 951 ->setHint($dependencyTable, '', false); 952 } 953 954 // trigger dependency UP 955 $dependencyTable = (new CTableInfo()) 956 ->setAttribute('style', 'width: 200px;') 957 ->addRow(bold(_('Dependent').':')); 958 959 $isDependencyFound = false; 960 $dbDependencies = DBselect('SELECT td.* FROM trigger_depends td WHERE td.triggerid_up='.zbx_dbstr($triggerId)); 961 while ($dbDependency = DBfetch($dbDependencies)) { 962 $dependencyTable->addRow(SPACE.'-'.SPACE.CMacrosResolverHelper::resolveTriggerNameById($dbDependency['triggerid_down'])); 963 $isDependencyFound = true; 964 } 965 966 if ($isDependencyFound) { 967 $desc[] = (new CSpan()) 968 ->addClass(ZBX_STYLE_ICON_DEPEND_UP) 969 ->setHint($dependencyTable, '', false); 970 } 971 } 972 973 $column = new CCol([$desc, $ack]); 974 975 if ($css !== null) { 976 $column 977 ->addClass($css) 978 ->addClass(ZBX_STYLE_CURSOR_POINTER); 979 } 980 981 if ($trigger && $config['blink_period'] > 0 && time() - $trigger['lastchange'] < $config['blink_period']) { 982 $column->addClass('blink'); 983 $column->setAttribute('data-toggle-class', $css); 984 } 985 986 if ($trigger) { 987 $column->setMenuPopup(CMenuPopupHelper::getTrigger($trigger, $acknowledge)); 988 } 989 990 return $column; 991} 992 993/** 994 * Calculate trigger availability. 995 * 996 * @param int $triggerId trigger id 997 * @param int $startTime begin period 998 * @param int $endTime end period 999 * 1000 * @return array 1001 */ 1002function calculateAvailability($triggerId, $startTime, $endTime) { 1003 $startValue = TRIGGER_VALUE_FALSE; 1004 1005 if ($startTime > 0 && $startTime <= time()) { 1006 $sql = 'SELECT e.eventid,e.value'. 1007 ' FROM events e'. 1008 ' WHERE e.objectid='.zbx_dbstr($triggerId). 1009 ' AND e.source='.EVENT_SOURCE_TRIGGERS. 1010 ' AND e.object='.EVENT_OBJECT_TRIGGER. 1011 ' AND e.clock<'.zbx_dbstr($startTime). 1012 ' ORDER BY e.eventid DESC'; 1013 if ($row = DBfetch(DBselect($sql, 1))) { 1014 $startValue = $row['value']; 1015 } 1016 1017 $min = $startTime; 1018 } 1019 1020 $sql = 'SELECT COUNT(e.eventid) AS cnt,MIN(e.clock) AS min_clock,MAX(e.clock) AS max_clock'. 1021 ' FROM events e'. 1022 ' WHERE e.objectid='.zbx_dbstr($triggerId). 1023 ' AND e.source='.EVENT_SOURCE_TRIGGERS. 1024 ' AND e.object='.EVENT_OBJECT_TRIGGER; 1025 if ($startTime) { 1026 $sql .= ' AND e.clock>='.zbx_dbstr($startTime); 1027 } 1028 if ($endTime) { 1029 $sql .= ' AND e.clock<='.zbx_dbstr($endTime); 1030 } 1031 1032 $dbEvents = DBfetch(DBselect($sql)); 1033 if ($dbEvents['cnt'] > 0) { 1034 if (!isset($min)) { 1035 $min = $dbEvents['min_clock']; 1036 } 1037 $max = $dbEvents['max_clock']; 1038 } 1039 else { 1040 if ($startTime == 0 && $endTime == 0) { 1041 $max = time(); 1042 $min = $max - SEC_PER_DAY; 1043 } 1044 else { 1045 $ret['true_time'] = 0; 1046 $ret['false_time'] = 0; 1047 $ret['true'] = (TRIGGER_VALUE_TRUE == $startValue) ? 100 : 0; 1048 $ret['false'] = (TRIGGER_VALUE_FALSE == $startValue) ? 100 : 0; 1049 return $ret; 1050 } 1051 } 1052 1053 $state = $startValue; 1054 $true_time = 0; 1055 $false_time = 0; 1056 $time = $min; 1057 if ($startTime == 0 && $endTime == 0) { 1058 $max = time(); 1059 } 1060 if ($endTime == 0) { 1061 $endTime = $max; 1062 } 1063 1064 $rows = 0; 1065 $dbEvents = DBselect( 1066 'SELECT e.eventid,e.clock,e.value'. 1067 ' FROM events e'. 1068 ' WHERE e.objectid='.zbx_dbstr($triggerId). 1069 ' AND e.source='.EVENT_SOURCE_TRIGGERS. 1070 ' AND e.object='.EVENT_OBJECT_TRIGGER. 1071 ' AND e.clock BETWEEN '.$min.' AND '.$max. 1072 ' ORDER BY e.eventid' 1073 ); 1074 while ($row = DBfetch($dbEvents)) { 1075 $clock = $row['clock']; 1076 $value = $row['value']; 1077 1078 $diff = max($clock - $time, 0); 1079 $time = $clock; 1080 1081 if ($state == 0) { 1082 $false_time += $diff; 1083 $state = $value; 1084 } 1085 elseif ($state == 1) { 1086 $true_time += $diff; 1087 $state = $value; 1088 } 1089 $rows++; 1090 } 1091 1092 if ($rows == 0) { 1093 $trigger = get_trigger_by_triggerid($triggerId); 1094 $state = $trigger['value']; 1095 } 1096 1097 if ($state == TRIGGER_VALUE_FALSE) { 1098 $false_time = $false_time + $endTime - $time; 1099 } 1100 elseif ($state == TRIGGER_VALUE_TRUE) { 1101 $true_time = $true_time + $endTime - $time; 1102 } 1103 $total_time = $true_time + $false_time; 1104 1105 if ($total_time == 0) { 1106 $ret['true_time'] = 0; 1107 $ret['false_time'] = 0; 1108 $ret['true'] = 0; 1109 $ret['false'] = 0; 1110 } 1111 else { 1112 $ret['true_time'] = $true_time; 1113 $ret['false_time'] = $false_time; 1114 $ret['true'] = (100 * $true_time) / $total_time; 1115 $ret['false'] = (100 * $false_time) / $total_time; 1116 } 1117 1118 return $ret; 1119} 1120 1121function get_triggers_unacknowledged($db_element, $count_problems = null, $ack = false) { 1122 $elements = [ 1123 'hosts' => [], 1124 'hosts_groups' => [], 1125 'triggers' => [] 1126 ]; 1127 1128 get_map_elements($db_element, $elements); 1129 if (empty($elements['hosts_groups']) && empty($elements['hosts']) && empty($elements['triggers'])) { 1130 return 0; 1131 } 1132 1133 $config = select_config(); 1134 1135 $options = [ 1136 'monitored' => true, 1137 'countOutput' => true, 1138 'filter' => [], 1139 'limit' => $config['search_limit'] + 1 1140 ]; 1141 1142 if ($ack) { 1143 $options['withAcknowledgedEvents'] = 1; 1144 } 1145 else { 1146 $options['withUnacknowledgedEvents'] = 1; 1147 } 1148 1149 if ($count_problems) { 1150 $options['filter']['value'] = TRIGGER_VALUE_TRUE; 1151 } 1152 if (!empty($elements['hosts_groups'])) { 1153 $options['groupids'] = array_unique($elements['hosts_groups']); 1154 } 1155 if (!empty($elements['hosts'])) { 1156 $options['hostids'] = array_unique($elements['hosts']); 1157 } 1158 if (!empty($elements['triggers'])) { 1159 $options['triggerids'] = array_unique($elements['triggers']); 1160 } 1161 1162 return API::Trigger()->get($options); 1163} 1164 1165function make_trigger_details($trigger) { 1166 $hostNames = []; 1167 1168 $config = select_config(); 1169 1170 $hostIds = zbx_objectValues($trigger['hosts'], 'hostid'); 1171 1172 $hosts = API::Host()->get([ 1173 'output' => ['name', 'hostid', 'status'], 1174 'hostids' => $hostIds, 1175 'selectScreens' => API_OUTPUT_COUNT, 1176 'selectGraphs' => API_OUTPUT_COUNT 1177 ]); 1178 1179 if (count($hosts) > 1) { 1180 order_result($hosts, 'name', ZBX_SORT_UP); 1181 } 1182 1183 $scripts = API::Script()->getScriptsByHosts($hostIds); 1184 1185 foreach ($hosts as $host) { 1186 $hostNames[] = (new CSpan($host['name'])) 1187 ->setMenuPopup(CMenuPopupHelper::getHost($host, $scripts[$host['hostid']])) 1188 ->addClass(ZBX_STYLE_LINK_ACTION); 1189 $hostNames[] = ', '; 1190 } 1191 array_pop($hostNames); 1192 1193 $expression = CMacrosResolverHelper::resolveTriggerExpression($trigger['expression'], 1194 ['html' => true, 'resolve_usermacros' => true, 'resolve_macros' => true]); 1195 1196 $table = (new CTableInfo()) 1197 ->addRow([ 1198 new CCol(_n('Host', 'Hosts', count($hosts))), 1199 new CCol($hostNames) 1200 ]) 1201 ->addRow([ 1202 new CCol(_('Trigger')), 1203 new CCol(CMacrosResolverHelper::resolveTriggerName($trigger)) 1204 ]) 1205 ->addRow([ 1206 _('Severity'), 1207 getSeverityCell($trigger['priority'], $config) 1208 ]) 1209 ->addRow([ 1210 new CCol(_('Expression')), 1211 new CCol($expression) 1212 ]) 1213 ->addRow([_('Event generation'), _('Normal').((TRIGGER_MULT_EVENT_ENABLED == $trigger['type']) 1214 ? SPACE.'+'.SPACE._('Multiple PROBLEM events') : '')]) 1215 ->addRow([_('Enabled'), (($trigger['status'] == TRIGGER_STATUS_ENABLED) 1216 ? (new CCol(_('Yes')))->addClass(ZBX_STYLE_GREEN) : (new CCol(_('No')))->addClass(ZBX_STYLE_RED)) 1217 ]); 1218 1219 return $table; 1220} 1221 1222/** 1223 * Analyze an expression and returns expression html tree 1224 * 1225 * @param string $expression 1226 * 1227 * @return array 1228 */ 1229function analyzeExpression($expression) { 1230 if (empty($expression)) { 1231 return ['', null]; 1232 } 1233 1234 $expressionData = new CTriggerExpression(); 1235 if (!$expressionData->parse($expression)) { 1236 error($expressionData->error); 1237 return false; 1238 } 1239 1240 $expressionTree[] = getExpressionTree($expressionData, 0, strlen($expressionData->expression) - 1); 1241 1242 $next = []; 1243 $letterNum = 0; 1244 return buildExpressionHtmlTree($expressionTree, $next, $letterNum); 1245} 1246 1247/** 1248 * Builds expression html tree 1249 * 1250 * @param array $expressionTree output of getExpressionTree() function 1251 * @param array $next parameter only for recursive call; should be empty array 1252 * @param int $letterNum parameter only for recursive call; should be 0 1253 * @param int $level parameter only for recursive call 1254 * @param string $operator parameter only for recursive call 1255 * 1256 * @return array array containing the trigger expression formula as the first element and an array describing the 1257 * expression tree as the second 1258 */ 1259function buildExpressionHtmlTree(array $expressionTree, array &$next, &$letterNum, $level = 0, $operator = null) { 1260 $treeList = []; 1261 $outline = ''; 1262 1263 end($expressionTree); 1264 $lastKey = key($expressionTree); 1265 1266 foreach ($expressionTree as $key => $element) { 1267 switch ($element['type']) { 1268 case 'operator': 1269 $next[$level] = ($key != $lastKey); 1270 $expr = expressionLevelDraw($next, $level); 1271 $expr[] = SPACE; 1272 $expr[] = ($element['operator'] === 'and') ? _('And') : _('Or'); 1273 $levelDetails = [ 1274 'list' => $expr, 1275 'id' => $element['id'], 1276 'expression' => [ 1277 'value' => $element['expression'] 1278 ] 1279 ]; 1280 1281 $levelErrors = expressionHighLevelErrors($element['expression']); 1282 if (count($levelErrors) > 0) { 1283 $levelDetails['expression']['levelErrors'] = $levelErrors; 1284 } 1285 $treeList[] = $levelDetails; 1286 1287 list($subOutline, $subTreeList) = buildExpressionHtmlTree($element['elements'], $next, $letterNum, 1288 $level + 1, $element['operator']); 1289 $treeList = array_merge($treeList, $subTreeList); 1290 1291 $outline .= ($level == 0) ? $subOutline : '('.$subOutline.')'; 1292 if ($operator !== null && $next[$level]) { 1293 $outline .= ' '.$operator.' '; 1294 } 1295 break; 1296 case 'expression': 1297 $next[$level] = ($key != $lastKey); 1298 1299 $letter = num2letter($letterNum++); 1300 $outline .= $letter; 1301 if ($operator !== null && $next[$level]) { 1302 $outline .= ' '.$operator.' '; 1303 } 1304 1305 if (defined('NO_LINK_IN_TESTING')) { 1306 $url = $element['expression']; 1307 } 1308 else { 1309 $expressionId = 'expr_'.$element['id']; 1310 1311 $url = (new CSpan($element['expression'])) 1312 ->addClass(ZBX_STYLE_LINK_ACTION) 1313 ->setId($expressionId) 1314 ->onClick('javascript: copy_expression("'.$expressionId.'");'); 1315 } 1316 $expr = expressionLevelDraw($next, $level); 1317 $expr[] = SPACE; 1318 $expr[] = bold($letter); 1319 $expr[] = SPACE; 1320 $expr[] = $url; 1321 1322 $levelDetails = [ 1323 'list' => $expr, 1324 'id' => $element['id'], 1325 'expression' => [ 1326 'value' => $element['expression'] 1327 ] 1328 ]; 1329 1330 $levelErrors = expressionHighLevelErrors($element['expression']); 1331 if (count($levelErrors) > 0) { 1332 $levelDetails['expression']['levelErrors'] = $levelErrors; 1333 } 1334 $treeList[] = $levelDetails; 1335 break; 1336 } 1337 } 1338 return [$outline, $treeList]; 1339} 1340 1341function expressionHighLevelErrors($expression) { 1342 static $errors, $definedErrorPhrases; 1343 1344 if (!isset($errors)) { 1345 $definedErrorPhrases = [ 1346 EXPRESSION_HOST_UNKNOWN => _('Unknown host, no such host present in system'), 1347 EXPRESSION_HOST_ITEM_UNKNOWN => _('Unknown host item, no such item in selected host'), 1348 EXPRESSION_NOT_A_MACRO_ERROR => _('Given expression is not a macro'), 1349 EXPRESSION_FUNCTION_UNKNOWN => _('Incorrect function is used'), 1350 EXPRESSION_UNSUPPORTED_VALUE_TYPE => _('Incorrect item value type') 1351 ]; 1352 $errors = []; 1353 } 1354 1355 if (!isset($errors[$expression])) { 1356 $errors[$expression] = []; 1357 $expressionData = new CTriggerExpression(); 1358 if ($expressionData->parse($expression)) { 1359 foreach ($expressionData->expressions as $exprPart) { 1360 $info = get_item_function_info($exprPart['expression']); 1361 1362 if (!is_array($info) && isset($definedErrorPhrases[$info])) { 1363 if (!isset($errors[$expression][$exprPart['expression']])) { 1364 $errors[$expression][$exprPart['expression']] = $definedErrorPhrases[$info]; 1365 } 1366 } 1367 } 1368 } 1369 } 1370 1371 $ret = []; 1372 if (count($errors[$expression]) == 0) { 1373 return $ret; 1374 } 1375 1376 $expressionData = new CTriggerExpression(); 1377 if ($expressionData->parse($expression)) { 1378 foreach ($expressionData->expressions as $exprPart) { 1379 if (isset($errors[$expression][$exprPart['expression']])) { 1380 $ret[$exprPart['expression']] = $errors[$expression][$exprPart['expression']]; 1381 } 1382 } 1383 } 1384 return $ret; 1385} 1386 1387/** 1388 * Draw level for trigger expression builder tree 1389 * 1390 * @param array $next 1391 * @param int $level 1392 * 1393 * @return array 1394 */ 1395function expressionLevelDraw(array $next, $level) { 1396 $expr = []; 1397 for ($i = 1; $i <= $level; $i++) { 1398 if ($i == $level) { 1399 $image = $next[$i] ? 'top_right_bottom' : 'top_right'; 1400 } 1401 else { 1402 $image = $next[$i] ? 'top_bottom' : 'space'; 1403 } 1404 $expr[] = new CImg('images/general/tr_'.$image.'.gif', 'tr', 12, 12); 1405 } 1406 return $expr; 1407} 1408 1409/** 1410 * Makes tree of expression elements 1411 * 1412 * Expression: 1413 * "{host1:system.cpu.util[,iowait].last(0)} > 50 and {host2:system.cpu.util[,iowait].last(0)} > 50" 1414 * Result: 1415 * array( 1416 * [0] => array( 1417 * 'id' => '0_94', 1418 * 'type' => 'operator', 1419 * 'operator' => 'and', 1420 * 'elements' => array( 1421 * [0] => array( 1422 * 'id' => '0_44', 1423 * 'type' => 'expression', 1424 * 'expression' => '{host1:system.cpu.util[,iowait].last(0)} > 50' 1425 * ), 1426 * [1] => array( 1427 * 'id' => '50_94', 1428 * 'type' => 'expression', 1429 * 'expression' => '{host2:system.cpu.util[,iowait].last(0)} > 50' 1430 * ) 1431 * ) 1432 * ) 1433 * ) 1434 * 1435 * @param CTriggerExpression $expressionData 1436 * @param int $start 1437 * @param int $end 1438 * 1439 * @return array 1440 */ 1441function getExpressionTree(CTriggerExpression $expressionData, $start, $end) { 1442 $blankSymbols = [' ', "\r", "\n", "\t"]; 1443 1444 $expressionTree = []; 1445 foreach (['or', 'and'] as $operator) { 1446 $operatorFound = false; 1447 $lParentheses = -1; 1448 $rParentheses = -1; 1449 $expressions = []; 1450 $openSymbolNum = $start; 1451 $operatorPos = 0; 1452 $operatorToken = ''; 1453 1454 for ($i = $start, $level = 0; $i <= $end; $i++) { 1455 switch ($expressionData->expression[$i]) { 1456 case ' ': 1457 case "\r": 1458 case "\n": 1459 case "\t": 1460 if ($openSymbolNum == $i) { 1461 $openSymbolNum++; 1462 } 1463 break; 1464 case '(': 1465 if ($level == 0) { 1466 $lParentheses = $i; 1467 } 1468 $level++; 1469 break; 1470 case ')': 1471 $level--; 1472 if ($level == 0) { 1473 $rParentheses = $i; 1474 } 1475 break; 1476 case '{': 1477 foreach ($expressionData->expressions as $exprPart) { 1478 if ($exprPart['pos'] == $i) { 1479 $i += strlen($exprPart['expression']) - 1; 1480 break; 1481 } 1482 } 1483 break; 1484 default: 1485 // try to parse an operator 1486 if ($operator[$operatorPos] === $expressionData->expression[$i]) { 1487 $operatorPos++; 1488 $operatorToken .= $expressionData->expression[$i]; 1489 1490 // operator found 1491 if ($operatorToken === $operator) { 1492 // we've reached the end of a complete expression, parse the expression on the left side of 1493 // the operator 1494 if ($level == 0) { 1495 // find the last symbol of the expression before the operator 1496 $closeSymbolNum = $i - strlen($operator); 1497 1498 // trim blank symbols after the expression 1499 while (in_array($expressionData->expression[$closeSymbolNum], $blankSymbols)) { 1500 $closeSymbolNum--; 1501 } 1502 1503 $expressions[] = getExpressionTree($expressionData, $openSymbolNum, $closeSymbolNum); 1504 $openSymbolNum = $i + 1; 1505 $operatorFound = true; 1506 } 1507 $operatorPos = 0; 1508 $operatorToken = ''; 1509 } 1510 } 1511 } 1512 } 1513 1514 // trim blank symbols in the end of the trigger expression 1515 $closeSymbolNum = $end; 1516 while (in_array($expressionData->expression[$closeSymbolNum], $blankSymbols)) { 1517 $closeSymbolNum--; 1518 } 1519 1520 // we've found a whole expression and parsed the expression on the left side of the operator, 1521 // parse the expression on the right 1522 if ($operatorFound) { 1523 $expressions[] = getExpressionTree($expressionData, $openSymbolNum, $closeSymbolNum); 1524 1525 // trim blank symbols in the beginning of the trigger expression 1526 $openSymbolNum = $start; 1527 while (in_array($expressionData->expression[$openSymbolNum], $blankSymbols)) { 1528 $openSymbolNum++; 1529 } 1530 1531 // trim blank symbols in the end of the trigger expression 1532 $closeSymbolNum = $end; 1533 while (in_array($expressionData->expression[$closeSymbolNum], $blankSymbols)) { 1534 $closeSymbolNum--; 1535 } 1536 1537 $expressionTree = [ 1538 'id' => $openSymbolNum.'_'.$closeSymbolNum, 1539 'expression' => substr($expressionData->expression, $openSymbolNum, $closeSymbolNum - $openSymbolNum + 1), 1540 'type' => 'operator', 1541 'operator' => $operator, 1542 'elements' => $expressions 1543 ]; 1544 break; 1545 } 1546 // if we've tried both operators and didn't find anything, it means there's only one expression 1547 // return the result 1548 elseif ($operator === 'and') { 1549 // trim extra parentheses 1550 if ($openSymbolNum == $lParentheses && $closeSymbolNum == $rParentheses) { 1551 $openSymbolNum++; 1552 $closeSymbolNum--; 1553 1554 $expressionTree = getExpressionTree($expressionData, $openSymbolNum, $closeSymbolNum); 1555 } 1556 // no extra parentheses remain, return the result 1557 else { 1558 $expressionTree = [ 1559 'id' => $openSymbolNum.'_'.$closeSymbolNum, 1560 'expression' => substr($expressionData->expression, $openSymbolNum, $closeSymbolNum - $openSymbolNum + 1), 1561 'type' => 'expression' 1562 ]; 1563 } 1564 } 1565 } 1566 1567 return $expressionTree; 1568} 1569 1570/** 1571 * Recreate an expression depending on action. 1572 * 1573 * Supported action values: 1574 * - and - add an expression using "and"; 1575 * - or - add an expression using "or"; 1576 * - r - replace; 1577 * - R - remove. 1578 * 1579 * @param string $expression 1580 * @param string $expressionId element identifier like "0_55" 1581 * @param string $action action to perform 1582 * @param string $newExpression expression for AND, OR or replace actions 1583 * 1584 * @return bool returns new expression or false if expression is incorrect 1585 */ 1586function remakeExpression($expression, $expressionId, $action, $newExpression) { 1587 if (empty($expression)) { 1588 return false; 1589 } 1590 1591 $expressionData = new CTriggerExpression(); 1592 if ($action != 'R' && !$expressionData->parse($newExpression)) { 1593 error($expressionData->error); 1594 return false; 1595 } 1596 1597 if (!$expressionData->parse($expression)) { 1598 error($expressionData->error); 1599 return false; 1600 } 1601 1602 $expressionTree[] = getExpressionTree($expressionData, 0, strlen($expressionData->expression) - 1); 1603 1604 if (rebuildExpressionTree($expressionTree, $expressionId, $action, $newExpression)) { 1605 $expression = makeExpression($expressionTree); 1606 } 1607 return $expression; 1608} 1609 1610/** 1611 * Rebuild expression depending on action. 1612 * 1613 * Supported action values: 1614 * - and - add an expression using "and"; 1615 * - or - add an expression using "or"; 1616 * - r - replace; 1617 * - R - remove. 1618 * 1619 * Example: 1620 * $expressionTree = array( 1621 * [0] => array( 1622 * 'id' => '0_94', 1623 * 'type' => 'operator', 1624 * 'operator' => 'and', 1625 * 'elements' => array( 1626 * [0] => array( 1627 * 'id' => '0_44', 1628 * 'type' => 'expression', 1629 * 'expression' => '{host1:system.cpu.util[,iowait].last(0)} > 50' 1630 * ), 1631 * [1] => array( 1632 * 'id' => '50_94', 1633 * 'type' => 'expression', 1634 * 'expression' => '{host2:system.cpu.util[,iowait].last(0)} > 50' 1635 * ) 1636 * ) 1637 * ) 1638 * ) 1639 * $action = 'R' 1640 * $expressionId = '50_94' 1641 * 1642 * Result: 1643 * $expressionTree = array( 1644 * [0] => array( 1645 * 'id' => '0_44', 1646 * 'type' => 'expression', 1647 * 'expression' => '{host1:system.cpu.util[,iowait].last(0)} > 50' 1648 * ) 1649 * ) 1650 * 1651 * @param array $expressionTree 1652 * @param string $expressionId element identifier like "0_55" 1653 * @param string $action action to perform 1654 * @param string $newExpression expression for AND, OR or replace actions 1655 * @param string $operator parameter only for recursive call 1656 * 1657 * @return bool returns true if element is found, false - otherwise 1658 */ 1659function rebuildExpressionTree(array &$expressionTree, $expressionId, $action, $newExpression, $operator = null) { 1660 foreach ($expressionTree as $key => $expression) { 1661 if ($expressionId == $expressionTree[$key]['id']) { 1662 switch ($action) { 1663 case 'and': 1664 case 'or': 1665 switch ($expressionTree[$key]['type']) { 1666 case 'operator': 1667 if ($expressionTree[$key]['operator'] == $action) { 1668 $expressionTree[$key]['elements'][] = [ 1669 'expression' => $newExpression, 1670 'type' => 'expression' 1671 ]; 1672 } 1673 else { 1674 $element = [ 1675 'type' => 'operator', 1676 'operator' => $action, 1677 'elements' => [ 1678 $expressionTree[$key], 1679 [ 1680 'expression' => $newExpression, 1681 'type' => 'expression' 1682 ] 1683 ] 1684 ]; 1685 $expressionTree[$key] = $element; 1686 } 1687 break; 1688 case 'expression': 1689 if (!$operator || $operator != $action) { 1690 $element = [ 1691 'type' => 'operator', 1692 'operator' => $action, 1693 'elements' => [ 1694 $expressionTree[$key], 1695 [ 1696 'expression' => $newExpression, 1697 'type' => 'expression' 1698 ] 1699 ] 1700 ]; 1701 $expressionTree[$key] = $element; 1702 } 1703 else { 1704 $expressionTree[] = [ 1705 'expression' => $newExpression, 1706 'type' => 'expression' 1707 ]; 1708 } 1709 break; 1710 } 1711 break; 1712 // replace 1713 case 'r': 1714 $expressionTree[$key]['expression'] = $newExpression; 1715 if ($expressionTree[$key]['type'] == 'operator') { 1716 $expressionTree[$key]['type'] = 'expression'; 1717 unset($expressionTree[$key]['operator'], $expressionTree[$key]['elements']); 1718 } 1719 break; 1720 // remove 1721 case 'R': 1722 unset($expressionTree[$key]); 1723 break; 1724 } 1725 return true; 1726 } 1727 1728 if ($expressionTree[$key]['type'] == 'operator') { 1729 if (rebuildExpressionTree($expressionTree[$key]['elements'], $expressionId, $action, $newExpression, 1730 $expressionTree[$key]['operator'])) { 1731 return true; 1732 } 1733 } 1734 } 1735 1736 return false; 1737} 1738 1739/** 1740 * Makes expression by expression tree 1741 * 1742 * Example: 1743 * $expressionTree = array( 1744 * [0] => array( 1745 * 'type' => 'operator', 1746 * 'operator' => 'and', 1747 * 'elements' => array( 1748 * [0] => array( 1749 * 'type' => 'expression', 1750 * 'expression' => '{host1:system.cpu.util[,iowait].last(0)} > 50' 1751 * ), 1752 * [1] => array( 1753 * 'type' => 'expression', 1754 * 'expression' => '{host2:system.cpu.util[,iowait].last(0)} > 50' 1755 * ) 1756 * ) 1757 * ) 1758 * ) 1759 * 1760 * Result: 1761 * "{host1:system.cpu.util[,iowait].last(0)} > 50 and {host2:system.cpu.util[,iowait].last(0)} > 50" 1762 * 1763 * @param array $expressionTree 1764 * @param int $level parameter only for recursive call 1765 * @param string $operator parameter only for recursive call 1766 * 1767 * @return string 1768 */ 1769function makeExpression(array $expressionTree, $level = 0, $operator = null) { 1770 $expression = ''; 1771 1772 end($expressionTree); 1773 $lastKey = key($expressionTree); 1774 1775 foreach ($expressionTree as $key => $element) { 1776 switch ($element['type']) { 1777 case 'operator': 1778 $subExpression = makeExpression($element['elements'], $level + 1, $element['operator']); 1779 1780 $expression .= ($level == 0) ? $subExpression : '('.$subExpression.')'; 1781 break; 1782 case 'expression': 1783 $expression .= $element['expression']; 1784 break; 1785 } 1786 if ($operator !== null && $key != $lastKey) { 1787 $expression .= ' '.$operator.' '; 1788 } 1789 } 1790 1791 return $expression; 1792} 1793 1794function get_item_function_info($expr) { 1795 $rule_float = [_('Numeric (float)'), 'preg_match("/^'.ZBX_PREG_NUMBER.'$/", {})']; 1796 $rule_int = [_('Numeric (integer)'), 'preg_match("/^'.ZBX_PREG_INT.'$/", {})']; 1797 $rule_0or1 = [_('0 or 1'), IN('0,1')]; 1798 $rules = [ 1799 // Every nested array should have two elements: label, validation. 1800 'integer' => [ 1801 ITEM_VALUE_TYPE_UINT64 => $rule_int 1802 ], 1803 'numeric' => [ 1804 ITEM_VALUE_TYPE_UINT64 => $rule_int, 1805 ITEM_VALUE_TYPE_FLOAT => $rule_float 1806 ], 1807 'numeric_as_float' => [ 1808 ITEM_VALUE_TYPE_UINT64 => $rule_float, 1809 ITEM_VALUE_TYPE_FLOAT => $rule_float 1810 ], 1811 'numeric_as_uint' => [ 1812 ITEM_VALUE_TYPE_UINT64 => $rule_int, 1813 ITEM_VALUE_TYPE_FLOAT => $rule_int 1814 ], 1815 'numeric_as_0or1' => [ 1816 ITEM_VALUE_TYPE_UINT64 => $rule_0or1, 1817 ITEM_VALUE_TYPE_FLOAT => $rule_0or1 1818 ], 1819 'string_as_0or1' => [ 1820 ITEM_VALUE_TYPE_TEXT => $rule_0or1, 1821 ITEM_VALUE_TYPE_STR => $rule_0or1, 1822 ITEM_VALUE_TYPE_LOG => $rule_0or1 1823 ], 1824 'string_as_uint' => [ 1825 ITEM_VALUE_TYPE_TEXT => $rule_int, 1826 ITEM_VALUE_TYPE_STR => $rule_int, 1827 ITEM_VALUE_TYPE_LOG => $rule_int 1828 ], 1829 'string_as_float' => [ 1830 ITEM_VALUE_TYPE_TEXT => $rule_float, 1831 ITEM_VALUE_TYPE_STR => $rule_float, 1832 ITEM_VALUE_TYPE_LOG => $rule_float 1833 ], 1834 'log_as_uint' => [ 1835 ITEM_VALUE_TYPE_LOG => $rule_int 1836 ], 1837 'log_as_0or1' => [ 1838 ITEM_VALUE_TYPE_LOG => $rule_0or1 1839 ], 1840 'date' => [ 1841 'any' => ['YYYYMMDD', '{}>=19700101&&{}<=99991231'] 1842 ], 1843 'time' => [ 1844 'any' => ['HHMMSS', 'preg_match("/^([01]?\d|2[0-3])([0-5]?\d)([0-5]?\d)$/", {})'] 1845 ], 1846 'day_of_month' => [ 1847 'any' => ['1-31', '{}>=1&&{}<=31'] 1848 ], 1849 'day_of_week' => [ 1850 'any' => ['1-7', IN('1,2,3,4,5,6,7')] 1851 ] 1852 ]; 1853 1854 $functions = [ 1855 'abschange' => $rules['numeric'] + $rules['string_as_0or1'], 1856 'avg' => $rules['numeric_as_float'], 1857 'band' => $rules['integer'], 1858 'change' => $rules['numeric'] + $rules['string_as_0or1'], 1859 'count' => $rules['numeric_as_uint'] + $rules['string_as_uint'], 1860 'date' => $rules['date'], 1861 'dayofmonth' => $rules['day_of_month'], 1862 'dayofweek' => $rules['day_of_week'], 1863 'delta' => $rules['numeric'], 1864 'diff' => $rules['numeric_as_0or1'] + $rules['string_as_0or1'], 1865 'forecast' => $rules['numeric_as_float'], 1866 'fuzzytime' => $rules['numeric_as_0or1'], 1867 'iregexp' => $rules['string_as_0or1'], 1868 'last' => $rules['numeric'] + $rules['string_as_float'], 1869 'logeventid' => $rules['log_as_0or1'], 1870 'logseverity' => $rules['log_as_uint'], 1871 'logsource' => $rules['log_as_0or1'], 1872 'max' => $rules['numeric'], 1873 'min' => $rules['numeric'], 1874 'nodata' => $rules['numeric_as_0or1'] + $rules['string_as_0or1'], 1875 'now' => $rules['numeric_as_uint'] + $rules['string_as_uint'], 1876 'percentile' => $rules['numeric'], 1877 'prev' => $rules['numeric'] + $rules['string_as_float'], 1878 'regexp' => $rules['string_as_0or1'], 1879 'str' => $rules['string_as_0or1'], 1880 'strlen' => $rules['string_as_uint'], 1881 'sum' => $rules['numeric'], 1882 'time' => $rules['time'], 1883 'timeleft' => $rules['numeric_as_float'] 1884 ]; 1885 1886 $expr_data = new CTriggerExpression(); 1887 $expression = $expr_data->parse($expr); 1888 1889 if (!$expression) { 1890 return EXPRESSION_NOT_A_MACRO_ERROR; 1891 } 1892 1893 switch (true) { 1894 case ($expression->hasTokenOfType(CTriggerExpressionParserResult::TOKEN_TYPE_MACRO)): 1895 $result = [ 1896 'type' => T_ZBX_STR, 1897 'value_type' => $rule_0or1[0], 1898 'validation' => $rule_0or1[1] 1899 ]; 1900 break; 1901 1902 case ($expression->hasTokenOfType(CTriggerExpressionParserResult::TOKEN_TYPE_USER_MACRO)): 1903 case ($expression->hasTokenOfType(CTriggerExpressionParserResult::TOKEN_TYPE_LLD_MACRO)): 1904 $result = [ 1905 'type' => T_ZBX_STR, 1906 'value_type' => $rule_float[0], 1907 'validation' => $rule_float[1] 1908 ]; 1909 break; 1910 1911 case ($expression->hasTokenOfType(CTriggerExpressionParserResult::TOKEN_TYPE_FUNCTION_MACRO)): 1912 $expr_part = reset($expr_data->expressions); 1913 1914 if (!array_key_exists($expr_part['functionName'], $functions)) { 1915 $result = EXPRESSION_FUNCTION_UNKNOWN; 1916 break; 1917 } 1918 1919 $host = API::Host()->get([ 1920 'output' => ['hostid'], 1921 'filter' => ['host' => [$expr_part['host']]], 1922 'templated_hosts' => true 1923 ]); 1924 1925 if (!$host) { 1926 $result = EXPRESSION_HOST_UNKNOWN; 1927 break; 1928 } 1929 1930 $item = API::Item()->get([ 1931 'output' => ['value_type'], 1932 'hostids' => $host[0]['hostid'], 1933 'filter' => [ 1934 'key_' => [$expr_part['item']] 1935 ], 1936 'webitems' => true 1937 ]); 1938 1939 if (!$item) { 1940 $item = API::ItemPrototype()->get([ 1941 'output' => ['value_type'], 1942 'hostids' => $host[0]['hostid'], 1943 'filter' => [ 1944 'key_' => [$expr_part['item']] 1945 ] 1946 ]); 1947 } 1948 1949 if (!$item) { 1950 $result = EXPRESSION_HOST_ITEM_UNKNOWN; 1951 break; 1952 } 1953 1954 $function = $functions[$expr_part['functionName']]; 1955 $value_type = $item[0]['value_type']; 1956 1957 if (array_key_exists('any', $function)) { 1958 $value_type = 'any'; 1959 } 1960 elseif (!array_key_exists($value_type, $function)) { 1961 $result = EXPRESSION_UNSUPPORTED_VALUE_TYPE; 1962 break; 1963 } 1964 1965 $result = [ 1966 'type' => T_ZBX_STR, 1967 'value_type' => $function[$value_type][0], 1968 'validation' => $function[$value_type][1] 1969 ]; 1970 break; 1971 1972 default: 1973 $result = EXPRESSION_NOT_A_MACRO_ERROR; 1974 break; 1975 } 1976 1977 return $result; 1978} 1979 1980/** 1981 * Substitute macros in the expression with the given values and evaluate its result. 1982 * 1983 * @param string $expression a trigger expression 1984 * @param array $replaceFunctionMacros an array of macro - value pairs 1985 * 1986 * @return bool the calculated value of the expression 1987 */ 1988function evalExpressionData($expression, $replaceFunctionMacros) { 1989 // Sort by longest array key which in this case contains macros. 1990 uksort($replaceFunctionMacros, function ($key1, $key2) { 1991 $s1 = strlen($key1); 1992 $s2 = strlen($key2); 1993 1994 if ($s1 == $s2) { 1995 return 0; 1996 } 1997 1998 return ($s1 > $s2) ? -1 : 1; 1999 }); 2000 2001 // replace function macros with their values 2002 $expression = str_replace(array_keys($replaceFunctionMacros), array_values($replaceFunctionMacros), $expression); 2003 2004 $parser = new CTriggerExpression(); 2005 $parseResult = $parser->parse($expression); 2006 2007 // The $replaceFunctionMacros array may contain string values which after substitution 2008 // will result in an invalid expression. In such cases we should just return false. 2009 if (!$parseResult) { 2010 return false; 2011 } 2012 2013 // turn the expression into valid PHP code 2014 $evStr = ''; 2015 $replaceOperators = ['not' => '!', '=' => '==']; 2016 foreach ($parseResult->getTokens() as $token) { 2017 $value = $token['value']; 2018 2019 switch ($token['type']) { 2020 case CTriggerExpressionParserResult::TOKEN_TYPE_OPERATOR: 2021 // replace specific operators with their PHP analogues 2022 if (isset($replaceOperators[$token['value']])) { 2023 $value = $replaceOperators[$token['value']]; 2024 } 2025 2026 break; 2027 case CTriggerExpressionParserResult::TOKEN_TYPE_NUMBER: 2028 // convert numeric values with suffixes 2029 if ($token['data']['suffix'] !== null) { 2030 $value = convert($value); 2031 } 2032 2033 $value = '((float) "'.$value.'")'; 2034 2035 break; 2036 } 2037 2038 $evStr .= ' '.$value; 2039 } 2040 2041 // execute expression 2042 eval('$result = ('.trim($evStr).');'); 2043 2044 return $result; 2045} 2046 2047function convert($value) { 2048 $value = trim($value); 2049 2050 if (!preg_match('/(?P<value>[\-+]?([.][0-9]+|[0-9]+[.]?[0-9]*))(?P<mult>['.ZBX_BYTE_SUFFIXES.ZBX_TIME_SUFFIXES.']?)/', 2051 $value, $arr)) { 2052 return $value; 2053 } 2054 2055 $value = $arr['value']; 2056 switch ($arr['mult']) { 2057 case 'T': 2058 $value *= 1024 * 1024 * 1024 * 1024; 2059 break; 2060 case 'G': 2061 $value *= 1024 * 1024 * 1024; 2062 break; 2063 case 'M': 2064 $value *= 1024 * 1024; 2065 break; 2066 case 'K': 2067 $value *= 1024; 2068 break; 2069 case 'm': 2070 $value *= 60; 2071 break; 2072 case 'h': 2073 $value *= 60 * 60; 2074 break; 2075 case 'd': 2076 $value *= 60 * 60 * 24; 2077 break; 2078 case 'w': 2079 $value *= 60 * 60 * 24 * 7; 2080 break; 2081 } 2082 2083 return $value; 2084} 2085 2086/** 2087 * Quoting $param if it contains special characters. 2088 * 2089 * @param string $param 2090 * @param bool $forced 2091 * 2092 * @return string 2093 */ 2094function quoteFunctionParam($param, $forced = false) { 2095 if (!$forced) { 2096 if (!isset($param[0]) || ($param[0] != '"' && false === strpbrk($param, ',)'))) { 2097 return $param; 2098 } 2099 } 2100 2101 return '"'.str_replace('"', '\\"', $param).'"'; 2102} 2103 2104/** 2105 * Returns the text indicating the trigger's status and state. If the $state parameter is not given, only the status of 2106 * the trigger will be taken into account. 2107 * 2108 * @param int $status 2109 * @param int $state 2110 * 2111 * @return string 2112 */ 2113function triggerIndicator($status, $state = null) { 2114 if ($status == TRIGGER_STATUS_ENABLED) { 2115 return ($state == TRIGGER_STATE_UNKNOWN) ? _('Unknown') : _('Enabled'); 2116 } 2117 elseif ($status == TRIGGER_STATUS_DISABLED) { 2118 return _('Disabled'); 2119 } 2120 2121 return _('Unknown'); 2122} 2123 2124/** 2125 * Returns the CSS class for the trigger's status and state indicator. If the $state parameter is not given, only the 2126 * status of the trigger will be taken into account. 2127 * 2128 * @param int $status 2129 * @param int $state 2130 * 2131 * @return string 2132 */ 2133function triggerIndicatorStyle($status, $state = null) { 2134 if ($status == TRIGGER_STATUS_ENABLED) { 2135 return ($state == TRIGGER_STATE_UNKNOWN) ? 2136 ZBX_STYLE_GREY : 2137 ZBX_STYLE_GREEN; 2138 } 2139 elseif ($status == TRIGGER_STATUS_DISABLED) { 2140 return ZBX_STYLE_RED; 2141 } 2142 2143 return ZBX_STYLE_GREY; 2144} 2145 2146/** 2147 * Orders triggers by both status and state. Triggers are sorted in the following order: enabled, disabled, unknown. 2148 * 2149 * Keep in sync with orderItemsByStatus(). 2150 * 2151 * @param array $triggers 2152 * @param string $sortorder 2153 */ 2154function orderTriggersByStatus(array &$triggers, $sortorder = ZBX_SORT_UP) { 2155 $sort = []; 2156 2157 foreach ($triggers as $key => $trigger) { 2158 if ($trigger['status'] == TRIGGER_STATUS_ENABLED) { 2159 $statusOrder = ($trigger['state'] == TRIGGER_STATE_UNKNOWN) ? 2 : 0; 2160 } 2161 elseif ($trigger['status'] == TRIGGER_STATUS_DISABLED) { 2162 $statusOrder = 1; 2163 } 2164 2165 $sort[$key] = $statusOrder; 2166 } 2167 2168 if ($sortorder == ZBX_SORT_UP) { 2169 asort($sort); 2170 } 2171 else { 2172 arsort($sort); 2173 } 2174 2175 $sortedTriggers = []; 2176 foreach ($sort as $key => $val) { 2177 $sortedTriggers[$key] = $triggers[$key]; 2178 } 2179 $triggers = $sortedTriggers; 2180} 2181