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 22/** 23 * A class to display problems as a screen element. 24 */ 25class CScreenProblem extends CScreenBase { 26 27 /** 28 * Data 29 * 30 * @var array 31 */ 32 public $data; 33 34 /** 35 * Init screen data. 36 * 37 * @param array $options 38 * @param array $options['data'] 39 */ 40 public function __construct(array $options = []) { 41 parent::__construct($options); 42 $this->data = array_key_exists('data', $options) ? $options['data'] : null; 43 44 if ($this->data['filter']['show'] == TRIGGERS_OPTION_ALL) { 45 $this->data['filter']['from'] = $this->timeline['from_ts']; 46 $this->data['filter']['to'] = $this->timeline['to_ts']; 47 } 48 } 49 50 /** 51 * Get problems from "events" table. 52 * 53 * @param array $options 54 * @param array|null $options['groupids'] 55 * @param array|null $options['hostids'] 56 * @param array|null $options['objectids'] 57 * @param string|null $options['eventid_till'] 58 * @param int|null $options['time_from'] 59 * @param int|null $options['time_till'] 60 * @param array $options['severities'] (optional) 61 * @param bool $options['acknowledged'] (optional) 62 * @param array $options['tags'] (optional) 63 * @param int $options['limit'] 64 * 65 * @static 66 * 67 * @return array 68 */ 69 private static function getDataEvents(array $options) { 70 return API::Event()->get([ 71 'output' => ['eventid', 'objectid', 'clock', 'ns', 'name', 'severity'], 72 'source' => EVENT_SOURCE_TRIGGERS, 73 'object' => EVENT_OBJECT_TRIGGER, 74 'value' => TRIGGER_VALUE_TRUE, 75 'sortfield' => ['eventid'], 76 'sortorder' => ZBX_SORT_DOWN, 77 'preservekeys' => true 78 ] + $options); 79 } 80 81 /** 82 * Get problems from "problem" table. 83 * 84 * @param array $options 85 * @param array|null $options['groupids'] 86 * @param array|null $options['hostids'] 87 * @param array|null $options['objectids'] 88 * @param string|null $options['eventid_till'] 89 * @param bool $options['recent'] 90 * @param array $options['severities'] (optional) 91 * @param bool $options['acknowledged'] (optional) 92 * @param int $options['time_from'] (optional) 93 * @param array $options['tags'] (optional) 94 * @param int $options['limit'] 95 * 96 * @static 97 * 98 * @return array 99 */ 100 private static function getDataProblems(array $options) { 101 return API::Problem()->get([ 102 'output' => ['eventid', 'objectid', 'clock', 'ns', 'name', 'severity'], 103 'source' => EVENT_SOURCE_TRIGGERS, 104 'object' => EVENT_OBJECT_TRIGGER, 105 'sortfield' => ['eventid'], 106 'sortorder' => ZBX_SORT_DOWN, 107 'preservekeys' => true 108 ] + $options); 109 } 110 111 /** 112 * Get problems from "problem" table. Return: 113 * [ 114 * 'problems' => [...], 115 * 'triggers' => [...] 116 * ] 117 * 118 * @param array $filter 119 * @param array $filter['groupids'] (optional) 120 * @param array $filter['exclude_groupids'] (optional) 121 * @param array $filter['hostids'] (optional) 122 * @param array $filter['triggerids'] (optional) 123 * @param array $filter['inventory'] (optional) 124 * @param string $filter['inventory'][]['field'] 125 * @param string $filter['inventory'][]['value'] 126 * @param string $filter['name'] (optional) 127 * @param int $filter['show'] TRIGGERS_OPTION_* 128 * @param int $filter['from'] (optional) usable together with 'to' and only for 129 * TRIGGERS_OPTION_ALL, timestamp. 130 * @param int $filter['to'] (optional) usable together with 'from' and only for 131 * TRIGGERS_OPTION_ALL, timestamp. 132 * @param int $filter['age_state'] (optional) usable together with 'age' and only for 133 * TRIGGERS_OPTION_(RECENT|IN)_PROBLEM 134 * @param int $filter['age'] (optional) usable together with 'age_state' and only for 135 * TRIGGERS_OPTION_(RECENT|IN)_PROBLEM 136 * @param array $filter['severities'] (optional) 137 * @param int $filter['unacknowledged'] (optional) 138 * @param array $filter['tags'] (optional) 139 * @param string $filter['tags'][]['tag'] 140 * @param string $filter['tags'][]['value'] 141 * @param int $filter['show_suppressed'] (optional) 142 * @param int $filter['show_opdata'] (optional) 143 * @param bool $resolve_comments 144 * 145 * @static 146 * 147 * @return array 148 */ 149 public static function getData(array $filter, bool $resolve_comments = false) { 150 $filter_groupids = array_key_exists('groupids', $filter) && $filter['groupids'] 151 ? getSubGroups($filter['groupids']) 152 : null; 153 $filter_hostids = array_key_exists('hostids', $filter) && $filter['hostids'] ? $filter['hostids'] : null; 154 $filter_triggerids = array_key_exists('triggerids', $filter) && $filter['triggerids'] 155 ? $filter['triggerids'] 156 : null; 157 $show_opdata = array_key_exists('show_opdata', $filter) && $filter['show_opdata'] != OPERATIONAL_DATA_SHOW_NONE; 158 159 if (array_key_exists('exclude_groupids', $filter) && $filter['exclude_groupids']) { 160 $exclude_groupids = getSubGroups($filter['exclude_groupids']); 161 162 if ($filter_hostids === null) { 163 // get all groups if no selected groups defined 164 if ($filter_groupids === null) { 165 $filter_groupids = array_keys(API::HostGroup()->get([ 166 'output' => [], 167 'real_hosts' => true, 168 'preservekeys' => true 169 ])); 170 } 171 172 $filter_groupids = array_diff($filter_groupids, $exclude_groupids); 173 174 // get available hosts 175 $filter_hostids = array_keys(API::Host()->get([ 176 'output' => [], 177 'groupids' => $filter_groupids, 178 'preservekeys' => true 179 ])); 180 } 181 182 $exclude_hostids = array_keys(API::Host()->get([ 183 'output' => [], 184 'groupids' => $exclude_groupids, 185 'preservekeys' => true 186 ])); 187 188 $filter_hostids = array_diff($filter_hostids, $exclude_hostids); 189 } 190 191 if (array_key_exists('inventory', $filter) && $filter['inventory']) { 192 $options = [ 193 'output' => [], 194 'groupids' => $filter_groupids, 195 'hostids' => $filter_hostids, 196 'preservekeys' => true 197 ]; 198 foreach ($filter['inventory'] as $field) { 199 $options['searchInventory'][$field['field']][] = $field['value']; 200 } 201 202 $hostids = array_keys(API::Host()->get($options)); 203 204 $filter_hostids = ($filter_hostids !== null) ? array_intersect($filter_hostids, $hostids) : $hostids; 205 } 206 207 $data = [ 208 'problems' => [], 209 'triggers' => [] 210 ]; 211 212 $seen_triggerids = []; 213 $eventid_till = null; 214 215 do { 216 $options = [ 217 'groupids' => $filter_groupids, 218 'hostids' => $filter_hostids, 219 'objectids' => $filter_triggerids, 220 'eventid_till' => $eventid_till, 221 'suppressed' => false, 222 'limit' => CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT) + 1 223 ]; 224 225 if (array_key_exists('name', $filter) && $filter['name'] !== '') { 226 $options['search']['name'] = $filter['name']; 227 } 228 229 if ($filter['show'] == TRIGGERS_OPTION_ALL) { 230 if (array_key_exists('from', $filter) && array_key_exists('to', $filter)) { 231 $options['time_from'] = $filter['from']; 232 $options['time_till'] = $filter['to']; 233 } 234 } 235 else { 236 $options['recent'] = ($filter['show'] == TRIGGERS_OPTION_RECENT_PROBLEM); 237 if (array_key_exists('age_state', $filter) && array_key_exists('age', $filter) 238 && $filter['age_state'] == 1) { 239 $options['time_from'] = time() - $filter['age'] * SEC_PER_DAY + 1; 240 } 241 } 242 if (array_key_exists('severities', $filter)) { 243 $filter_severities = implode(',', $filter['severities']); 244 $all_severities = implode(',', range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1)); 245 246 if ($filter_severities !== '' && $filter_severities !== $all_severities) { 247 $options['severities'] = $filter['severities']; 248 } 249 } 250 if (array_key_exists('unacknowledged', $filter) && $filter['unacknowledged']) { 251 $options['acknowledged'] = false; 252 } 253 if (array_key_exists('evaltype', $filter)) { 254 $options['evaltype'] = $filter['evaltype']; 255 } 256 if (array_key_exists('tags', $filter) && $filter['tags']) { 257 $options['tags'] = $filter['tags']; 258 } 259 if (array_key_exists('show_suppressed', $filter) && $filter['show_suppressed']) { 260 unset($options['suppressed']); 261 $options['selectSuppressionData'] = ['maintenanceid', 'suppress_until']; 262 } 263 264 $problems = ($filter['show'] == TRIGGERS_OPTION_ALL) 265 ? self::getDataEvents($options) 266 : self::getDataProblems($options); 267 268 $end_of_data = (count($problems) < CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT) + 1); 269 270 if ($problems) { 271 $eventid_till = end($problems)['eventid'] - 1; 272 $triggerids = []; 273 274 if (array_key_exists('show_suppressed', $filter) && $filter['show_suppressed']) { 275 self::addMaintenanceNames($problems); 276 } 277 278 foreach ($problems as $problem) { 279 if (!array_key_exists($problem['objectid'], $seen_triggerids)) { 280 $triggerids[$problem['objectid']] = true; 281 } 282 } 283 284 if ($triggerids) { 285 $seen_triggerids += $triggerids; 286 287 $options = [ 288 'output' => ['priority', 'manual_close'], 289 'selectHosts' => ['hostid'], 290 'triggerids' => array_keys($triggerids), 291 'monitored' => true, 292 'skipDependent' => ($filter['show'] == TRIGGERS_OPTION_ALL) ? null : true, 293 'preservekeys' => true 294 ]; 295 296 $details = (array_key_exists('details', $filter) && $filter['details'] == 1); 297 298 if ($show_opdata) { 299 $options['output'][] = 'opdata'; 300 $options['selectFunctions'] = ['itemid']; 301 } 302 303 if ($resolve_comments || $show_opdata || $details) { 304 $options['output'][] = 'expression'; 305 } 306 307 if ($show_opdata || $details) { 308 $options['output'] = array_merge($options['output'], ['recovery_mode', 'recovery_expression']); 309 } 310 311 if ($resolve_comments) { 312 $options['output'][] = 'comments'; 313 } 314 315 $data['triggers'] += API::Trigger()->get($options); 316 } 317 318 foreach ($problems as $eventid => $problem) { 319 if (!array_key_exists($problem['objectid'], $data['triggers'])) { 320 unset($problems[$eventid]); 321 } 322 } 323 324 $data['problems'] += $problems; 325 } 326 } 327 while (count($data['problems']) < CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT) + 1 && !$end_of_data); 328 329 $data['problems'] = array_slice($data['problems'], 0, CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT) + 1, 330 true 331 ); 332 333 if ($show_opdata && $data['triggers']) { 334 $items = API::Item()->get([ 335 'output' => ['itemid', 'hostid', 'name', 'key_', 'value_type', 'units'], 336 'selectValueMap' => ['mappings'], 337 'triggerids' => array_keys($data['triggers']), 338 'webitems' => true, 339 'preservekeys' => true 340 ]); 341 342 foreach ($data['triggers'] as &$trigger) { 343 foreach ($trigger['functions'] as $function) { 344 $trigger['items'][] = $items[$function['itemid']]; 345 } 346 unset($trigger['functions']); 347 } 348 unset($trigger); 349 } 350 351 return $data; 352 } 353 354 /** 355 * Adds maintenance names of suppressed problems. 356 * 357 * @param array $problems 358 * @param array $problems[]['suppression_data'] 359 * @param int $problems[]['suppression_data'][]['maintenanceid'] 360 * 361 * @static 362 */ 363 public static function addMaintenanceNames(array &$problems) { 364 $maintenanceids = []; 365 366 foreach ($problems as $problem) { 367 if (array_key_exists('suppression_data', $problem) && $problem['suppression_data']) { 368 foreach ($problem['suppression_data'] as $data) { 369 $maintenanceids[] = $data['maintenanceid']; 370 } 371 } 372 } 373 374 if ($maintenanceids) { 375 $maintenances = API::Maintenance()->get([ 376 'output' => ['name'], 377 'maintenanceids' => $maintenanceids, 378 'preservekeys' => true 379 ]); 380 381 foreach ($problems as &$problem) { 382 if (array_key_exists('suppression_data', $problem) && $problem['suppression_data']) { 383 foreach ($problem['suppression_data'] as &$data) { 384 $data['maintenance_name'] = array_key_exists($data['maintenanceid'], $maintenances) 385 ? $maintenances[$data['maintenanceid']]['name'] 386 : _('Inaccessible maintenance'); 387 } 388 unset($data); 389 } 390 } 391 unset($problem); 392 } 393 } 394 395 /** 396 * @param array $data 397 * @param array $data['problems'] 398 * @param array $data['triggers'] 399 * @param string $sort 400 * @param string $sortorder 401 * 402 * @static 403 * 404 * @return array 405 */ 406 public static function sortData(array $data, $sort, $sortorder) { 407 if (!$data['problems']) { 408 return $data; 409 } 410 411 $last_problem = end($data['problems']); 412 $data['problems'] = array_slice($data['problems'], 0, CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT), 413 true 414 ); 415 416 switch ($sort) { 417 case 'host': 418 $triggers_hosts_list = []; 419 foreach (getTriggersHostsList($data['triggers']) as $triggerid => $trigger_hosts) { 420 $triggers_hosts_list[$triggerid] = implode(', ', zbx_objectValues($trigger_hosts, 'name')); 421 } 422 423 foreach ($data['problems'] as &$problem) { 424 $problem['host'] = $triggers_hosts_list[$problem['objectid']]; 425 } 426 unset($problem); 427 428 $sort_fields = [ 429 ['field' => 'host', 'order' => $sortorder], 430 ['field' => 'clock', 'order' => ZBX_SORT_DOWN], 431 ['field' => 'ns', 'order' => ZBX_SORT_DOWN] 432 ]; 433 break; 434 435 case 'severity': 436 $sort_fields = [ 437 ['field' => 'severity', 'order' => $sortorder], 438 ['field' => 'clock', 'order' => ZBX_SORT_DOWN], 439 ['field' => 'ns', 'order' => ZBX_SORT_DOWN] 440 ]; 441 break; 442 443 case 'name': 444 $sort_fields = [ 445 ['field' => 'name', 'order' => $sortorder], 446 ['field' => 'objectid', 'order' => $sortorder], 447 ['field' => 'clock', 'order' => ZBX_SORT_DOWN], 448 ['field' => 'ns', 'order' => ZBX_SORT_DOWN] 449 ]; 450 break; 451 452 default: 453 $sort_fields = [ 454 ['field' => 'clock', 'order' => $sortorder], 455 ['field' => 'ns', 'order' => $sortorder] 456 ]; 457 } 458 CArrayHelper::sort($data['problems'], $sort_fields); 459 460 $data['problems'][$last_problem['eventid']] = $last_problem; 461 462 return $data; 463 } 464 465 /** 466 * @param array $eventids 467 * 468 * @static 469 * 470 * @return array 471 */ 472 private static function getExDataEvents(array $eventids) { 473 $events = API::Event()->get([ 474 'output' => ['eventid', 'r_eventid', 'acknowledged'], 475 'selectTags' => ['tag', 'value'], 476 'select_acknowledges' => ['userid', 'clock', 'message', 'action', 'old_severity', 'new_severity'], 477 'source' => EVENT_SOURCE_TRIGGERS, 478 'object' => EVENT_OBJECT_TRIGGER, 479 'eventids' => $eventids, 480 'preservekeys' => true 481 ]); 482 483 $r_eventids = []; 484 485 foreach ($events as $event) { 486 $r_eventids[$event['r_eventid']] = true; 487 } 488 unset($r_eventids[0]); 489 490 $r_events = $r_eventids 491 ? API::Event()->get([ 492 'output' => ['clock', 'ns', 'correlationid', 'userid'], 493 'source' => EVENT_SOURCE_TRIGGERS, 494 'object' => EVENT_OBJECT_TRIGGER, 495 'eventids' => array_keys($r_eventids), 496 'preservekeys' => true 497 ]) 498 : []; 499 500 foreach ($events as &$event) { 501 if (array_key_exists($event['r_eventid'], $r_events)) { 502 $event['r_clock'] = $r_events[$event['r_eventid']]['clock']; 503 $event['r_ns'] = $r_events[$event['r_eventid']]['ns']; 504 $event['correlationid'] = $r_events[$event['r_eventid']]['correlationid']; 505 $event['userid'] = $r_events[$event['r_eventid']]['userid']; 506 } 507 else { 508 $event['r_clock'] = 0; 509 $event['r_ns'] = 0; 510 $event['correlationid'] = 0; 511 $event['userid'] = 0; 512 } 513 } 514 unset($event); 515 516 return $events; 517 } 518 519 /** 520 * @param array $eventids 521 * 522 * @static 523 * 524 * @return array 525 */ 526 private static function getExDataProblems(array $eventids) { 527 return API::Problem()->get([ 528 'output' => ['eventid', 'r_eventid', 'r_clock', 'r_ns', 'correlationid', 'userid', 'acknowledged'], 529 'selectTags' => ['tag', 'value'], 530 'selectAcknowledges' => ['userid', 'clock', 'message', 'action', 'old_severity', 'new_severity'], 531 'source' => EVENT_SOURCE_TRIGGERS, 532 'object' => EVENT_OBJECT_TRIGGER, 533 'eventids' => $eventids, 534 'recent' => true, 535 'preservekeys' => true 536 ]); 537 } 538 539 /** 540 * @param array $data 541 * @param array $data['problems'] 542 * @param array $data['triggers'] 543 * @param array $filter 544 * @param int $filter['details'] 545 * @param int $filter['show'] 546 * @param int $filter['show_opdata'] 547 * @param bool $resolve_comments 548 * 549 * @static 550 * 551 * @return array 552 */ 553 public static function makeData(array $data, array $filter, bool $resolve_comments = false) { 554 // unset unused triggers 555 $triggerids = []; 556 557 foreach ($data['problems'] as $problem) { 558 $triggerids[$problem['objectid']] = true; 559 } 560 561 foreach ($data['triggers'] as $triggerid => $trigger) { 562 if (!array_key_exists($triggerid, $triggerids)) { 563 unset($data['triggers'][$triggerid]); 564 } 565 } 566 567 if (!$data['problems']) { 568 return $data; 569 } 570 571 // resolve macros 572 if ($filter['details'] == 1 || $filter['show_opdata'] != OPERATIONAL_DATA_SHOW_NONE) { 573 foreach ($data['triggers'] as &$trigger) { 574 $trigger['expression_html'] = $trigger['expression']; 575 $trigger['recovery_expression_html'] = $trigger['recovery_expression']; 576 } 577 unset($trigger); 578 579 $data['triggers'] = CMacrosResolverHelper::resolveTriggerExpressions($data['triggers'], [ 580 'html' => true, 581 'resolve_usermacros' => true, 582 'resolve_macros' => true, 583 'sources' => ['expression_html', 'recovery_expression_html'] 584 ]); 585 586 // Sort items. 587 if ($filter['show_opdata'] != OPERATIONAL_DATA_SHOW_NONE) { 588 $data['triggers'] = CMacrosResolverHelper::sortItemsByExpressionOrder($data['triggers']); 589 590 foreach ($data['triggers'] as &$trigger) { 591 $trigger['items'] = CMacrosResolverHelper::resolveItemNames($trigger['items']); 592 } 593 unset($trigger); 594 } 595 } 596 597 if ($resolve_comments) { 598 foreach ($data['problems'] as &$problem) { 599 $trigger = $data['triggers'][$problem['objectid']]; 600 $problem['comments'] = CMacrosResolverHelper::resolveTriggerDescription( 601 [ 602 'triggerid' => $problem['objectid'], 603 'expression' => $trigger['expression'], 604 'comments' => $trigger['comments'], 605 'clock' => $problem['clock'], 606 'ns' => $problem['ns'] 607 ], 608 ['events' => true] 609 ); 610 } 611 unset($problem); 612 613 foreach ($data['triggers'] as &$trigger) { 614 unset($trigger['comments']); 615 } 616 unset($trigger); 617 } 618 619 // get additional data 620 $eventids = array_keys($data['problems']); 621 622 $problems_data = ($filter['show'] == TRIGGERS_OPTION_ALL) 623 ? self::getExDataEvents($eventids) 624 : self::getExDataProblems($eventids); 625 626 $correlationids = []; 627 $userids = []; 628 629 foreach ($data['problems'] as $eventid => &$problem) { 630 if (array_key_exists($eventid, $problems_data)) { 631 $problem_data = $problems_data[$eventid]; 632 633 $problem['r_eventid'] = $problem_data['r_eventid']; 634 $problem['r_clock'] = $problem_data['r_clock']; 635 $problem['r_ns'] = $problem_data['r_ns']; 636 $problem['acknowledges'] = $problem_data['acknowledges']; 637 $problem['tags'] = $problem_data['tags']; 638 $problem['correlationid'] = $problem_data['correlationid']; 639 $problem['userid'] = $problem_data['userid']; 640 $problem['acknowledged'] = $problem_data['acknowledged']; 641 642 if ($problem['correlationid'] != 0) { 643 $correlationids[$problem['correlationid']] = true; 644 } 645 if ($problem['userid'] != 0) { 646 $userids[$problem['userid']] = true; 647 } 648 } 649 else { 650 unset($data['problems'][$eventid]); 651 } 652 } 653 unset($problem); 654 655 // Possible performance improvement: one API call may be saved, if r_clock for problem will be used. 656 $actions = getEventsActionsIconsData($data['problems'], $data['triggers']); 657 $data['actions'] = $actions['data']; 658 659 $data['correlations'] = $correlationids 660 ? API::Correlation()->get([ 661 'output' => ['name'], 662 'correlationids' => array_keys($correlationids), 663 'preservekeys' => true 664 ]) 665 : []; 666 667 $userids = $userids + $actions['userids']; 668 $data['users'] = $userids 669 ? API::User()->get([ 670 'output' => ['username', 'name', 'surname'], 671 'userids' => array_keys($userids + $actions['userids']), 672 'preservekeys' => true 673 ]) 674 : []; 675 676 return $data; 677 } 678 679 /** 680 * Add timeline breakpoint to a table if needed. 681 * 682 * @param CTableInfo $table 683 * @param int $last_clock timestamp of the previous record 684 * @param int $clock timestamp of the current record 685 * @param string $sortorder 686 * 687 * @static 688 */ 689 public static function addTimelineBreakpoint(CTableInfo $table, $last_clock, $clock, $sortorder) { 690 if ($sortorder === ZBX_SORT_UP) { 691 list($clock, $last_clock) = [$last_clock, $clock]; 692 } 693 694 $breakpoint = null; 695 $today = strtotime('today'); 696 $yesterday = strtotime('yesterday'); 697 $this_year = strtotime('first day of January '.date('Y', $today)); 698 699 if ($last_clock >= $today) { 700 if ($clock < $today) { 701 $breakpoint = _('Today'); 702 } 703 elseif (strftime('%H', $last_clock) != strftime('%H', $clock)) { 704 $breakpoint = strftime('%H:00', $last_clock); 705 } 706 } 707 elseif ($last_clock >= $yesterday) { 708 if ($clock < $yesterday) { 709 $breakpoint = _('Yesterday'); 710 } 711 } 712 elseif ($last_clock >= $this_year && $clock < $this_year) { 713 $breakpoint = strftime('%Y', $last_clock); 714 } 715 elseif (strftime('%Y%m', $last_clock) != strftime('%Y%m', $clock)) { 716 $breakpoint = getMonthCaption(strftime('%m', $last_clock)); 717 } 718 719 if ($breakpoint !== null) { 720 $table->addRow((new CRow([ 721 (new CCol(new CTag('h4', true, $breakpoint)))->addClass(ZBX_STYLE_TIMELINE_DATE), 722 (new CCol()) 723 ->addClass(ZBX_STYLE_TIMELINE_AXIS) 724 ->addClass(ZBX_STYLE_TIMELINE_DOT_BIG), 725 (new CCol())->addClass(ZBX_STYLE_TIMELINE_TD), 726 (new CCol())->setColSpan($table->getNumCols() - 3) 727 ]))->addClass(ZBX_STYLE_HOVER_NOBG)); 728 } 729 } 730 731 /** 732 * Process screen. 733 * 734 * @return string|CDiv (screen inside container) 735 */ 736 public function get() { 737 $this->dataId = 'problem'; 738 739 $url = (new CUrl('zabbix.php'))->setArgument('action', 'problem.view'); 740 $args = [ 741 'sort' => $this->data['sort'], 742 'sortorder' => $this->data['sortorder'] 743 ] + $this->data['filter']; 744 745 if ($this->data['filter']['show'] == TRIGGERS_OPTION_ALL) { 746 $args['from'] = $this->timeline['from']; 747 $args['to'] = $this->timeline['to']; 748 } 749 750 if (array_key_exists('severities', $args)) { 751 $args['severities'] = array_combine($args['severities'], $args['severities']); 752 } 753 754 array_map([$url, 'setArgument'], array_keys($args), $args); 755 756 $data = self::getData($this->data['filter'], true); 757 $data = self::sortData($data, $this->data['sort'], $this->data['sortorder']); 758 759 if ($this->data['action'] === 'problem.view') { 760 $paging = CPagerHelper::paginate($this->page, $data['problems'], ZBX_SORT_UP, $url); 761 } 762 763 $data = self::makeData($data, $this->data['filter'], true); 764 765 if ($data['triggers']) { 766 $triggerids = array_keys($data['triggers']); 767 768 $db_triggers = API::Trigger()->get([ 769 'output' => [], 770 'selectDependencies' => ['triggerid'], 771 'triggerids' => $triggerids, 772 'preservekeys' => true 773 ]); 774 775 foreach ($data['triggers'] as $triggerid => &$trigger) { 776 $trigger['dependencies'] = array_key_exists($triggerid, $db_triggers) 777 ? $db_triggers[$triggerid]['dependencies'] 778 : []; 779 } 780 unset($trigger); 781 } 782 783 if ($data['problems']) { 784 $triggers_hosts = getTriggersHostsList($data['triggers']); 785 } 786 787 $show_opdata = $this->data['filter']['compact_view'] 788 ? OPERATIONAL_DATA_SHOW_NONE 789 : $this->data['filter']['show_opdata']; 790 791 if ($this->data['action'] === 'problem.view') { 792 $form = (new CForm('post', 'zabbix.php')) 793 ->setId('problem_form') 794 ->setName('problem') 795 ->cleanItems(); 796 797 $header_check_box = (new CColHeader( 798 (new CCheckBox('all_eventids')) 799 ->onClick("checkAll('".$form->getName()."', 'all_eventids', 'eventids');") 800 )); 801 802 $this->data['filter']['compact_view'] 803 ? $header_check_box->addStyle('width: 20px;') 804 : $header_check_box->addClass(ZBX_STYLE_CELL_WIDTH); 805 806 $link = $url->getUrl(); 807 808 $show_timeline = ($this->data['sort'] === 'clock' && !$this->data['filter']['compact_view'] 809 && $this->data['filter']['show_timeline']); 810 811 $show_recovery_data = in_array($this->data['filter']['show'], [ 812 TRIGGERS_OPTION_RECENT_PROBLEM, 813 TRIGGERS_OPTION_ALL 814 ]); 815 $header_clock = 816 make_sorting_header(_('Time'), 'clock', $this->data['sort'], $this->data['sortorder'], $link); 817 818 $this->data['filter']['compact_view'] 819 ? $header_clock->addStyle('width: 115px;') 820 : $header_clock->addClass(ZBX_STYLE_CELL_WIDTH); 821 822 if ($show_timeline) { 823 $header = [ 824 $header_clock->addClass(ZBX_STYLE_RIGHT), 825 (new CColHeader())->addClass(ZBX_STYLE_TIMELINE_TH), 826 (new CColHeader())->addClass(ZBX_STYLE_TIMELINE_TH) 827 ]; 828 } 829 else { 830 $header = [$header_clock]; 831 } 832 833 // Create table. 834 if ($this->data['filter']['compact_view']) { 835 if ($this->data['filter']['show_tags'] == PROBLEMS_SHOW_TAGS_NONE) { 836 $tags_header = null; 837 } 838 else { 839 $tags_header = (new CColHeader(_('Tags'))); 840 841 switch ($this->data['filter']['show_tags']) { 842 case PROBLEMS_SHOW_TAGS_1: 843 $tags_header->addClass(ZBX_STYLE_COLUMN_TAGS_1); 844 break; 845 case PROBLEMS_SHOW_TAGS_2: 846 $tags_header->addClass(ZBX_STYLE_COLUMN_TAGS_2); 847 break; 848 case PROBLEMS_SHOW_TAGS_3: 849 $tags_header->addClass(ZBX_STYLE_COLUMN_TAGS_3); 850 break; 851 } 852 } 853 854 $table = (new CTableInfo()) 855 ->setHeader(array_merge($header, [ 856 $header_check_box, 857 make_sorting_header(_('Severity'), 'severity', $this->data['sort'], $this->data['sortorder'], 858 $link 859 )->addStyle('width: 120px;'), 860 $show_recovery_data ? (new CColHeader(_('Recovery time')))->addStyle('width: 115px;') : null, 861 $show_recovery_data ? (new CColHeader(_('Status')))->addStyle('width: 70px;') : null, 862 (new CColHeader(_('Info')))->addStyle('width: 24px;'), 863 make_sorting_header(_('Host'), 'host', $this->data['sort'], $this->data['sortorder'], $link) 864 ->addStyle('width: 42%;'), 865 make_sorting_header(_('Problem'), 'name', $this->data['sort'], $this->data['sortorder'], $link) 866 ->addStyle('width: 58%;'), 867 (new CColHeader(_('Duration')))->addStyle('width: 73px;'), 868 (new CColHeader(_('Ack')))->addStyle('width: 36px;'), 869 (new CColHeader(_('Actions')))->addStyle('width: 64px;'), 870 $tags_header 871 ])) 872 ->addClass(ZBX_STYLE_COMPACT_VIEW) 873 ->addClass(ZBX_STYLE_OVERFLOW_ELLIPSIS); 874 } 875 else { 876 $table = (new CTableInfo()) 877 ->setHeader(array_merge($header, [ 878 $header_check_box, 879 make_sorting_header(_('Severity'), 'severity', $this->data['sort'], $this->data['sortorder'], 880 $link 881 ), 882 $show_recovery_data 883 ? (new CColHeader(_('Recovery time')))->addClass(ZBX_STYLE_CELL_WIDTH) 884 : null, 885 $show_recovery_data ? _('Status') : null, 886 _('Info'), 887 make_sorting_header(_('Host'), 'host', $this->data['sort'], $this->data['sortorder'], $link), 888 make_sorting_header(_('Problem'), 'name', $this->data['sort'], $this->data['sortorder'], $link), 889 ($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) 890 ? _('Operational data') 891 : null, 892 _('Duration'), 893 _('Ack'), 894 _('Actions'), 895 $this->data['filter']['show_tags'] ? _('Tags') : null 896 ])); 897 } 898 899 if ($this->data['filter']['show_tags']) { 900 $tags = makeTags($data['problems'], true, 'eventid', $this->data['filter']['show_tags'], 901 array_key_exists('tags', $this->data['filter']) ? $this->data['filter']['tags'] : [], 902 $this->data['filter']['tag_name_format'], $this->data['filter']['tag_priority'] 903 ); 904 } 905 906 if ($data['problems']) { 907 $triggers_hosts = makeTriggersHostsList($triggers_hosts); 908 } 909 910 $last_clock = 0; 911 $today = strtotime('today'); 912 913 // Make trigger dependencies. 914 if ($data['triggers']) { 915 $dependencies = getTriggerDependencies($data['triggers']); 916 } 917 918 $allowed = [ 919 'add_comments' => CWebUser::checkAccess(CRoleHelper::ACTIONS_ADD_PROBLEM_COMMENTS), 920 'change_severity' => CWebUser::checkAccess(CRoleHelper::ACTIONS_CHANGE_SEVERITY), 921 'acknowledge' => CWebUser::checkAccess(CRoleHelper::ACTIONS_ACKNOWLEDGE_PROBLEMS), 922 'close' => CWebUser::checkAccess(CRoleHelper::ACTIONS_CLOSE_PROBLEMS) 923 ]; 924 925 // Add problems to table. 926 foreach ($data['problems'] as $eventid => $problem) { 927 $trigger = $data['triggers'][$problem['objectid']]; 928 929 $cell_clock = ($problem['clock'] >= $today) 930 ? zbx_date2str(TIME_FORMAT_SECONDS, $problem['clock']) 931 : zbx_date2str(DATE_TIME_FORMAT_SECONDS, $problem['clock']); 932 $cell_clock = new CCol(new CLink($cell_clock, 933 (new CUrl('tr_events.php')) 934 ->setArgument('triggerid', $problem['objectid']) 935 ->setArgument('eventid', $problem['eventid']) 936 )); 937 938 if ($problem['r_eventid'] != 0) { 939 $cell_r_clock = ($problem['r_clock'] >= $today) 940 ? zbx_date2str(TIME_FORMAT_SECONDS, $problem['r_clock']) 941 : zbx_date2str(DATE_TIME_FORMAT_SECONDS, $problem['r_clock']); 942 $cell_r_clock = (new CCol(new CLink($cell_r_clock, 943 (new CUrl('tr_events.php')) 944 ->setArgument('triggerid', $problem['objectid']) 945 ->setArgument('eventid', $problem['eventid']) 946 ))) 947 ->addClass(ZBX_STYLE_NOWRAP) 948 ->addClass(ZBX_STYLE_RIGHT); 949 } 950 else { 951 $cell_r_clock = ''; 952 } 953 954 $can_be_closed = ($trigger['manual_close'] == ZBX_TRIGGER_MANUAL_CLOSE_ALLOWED && $allowed['close']); 955 956 if ($problem['r_eventid'] != 0) { 957 $value = TRIGGER_VALUE_FALSE; 958 $value_str = _('RESOLVED'); 959 $value_clock = $problem['r_clock']; 960 $can_be_closed = false; 961 } 962 else { 963 $in_closing = false; 964 965 foreach ($problem['acknowledges'] as $acknowledge) { 966 if (($acknowledge['action'] & ZBX_PROBLEM_UPDATE_CLOSE) == ZBX_PROBLEM_UPDATE_CLOSE) { 967 $in_closing = true; 968 $can_be_closed = false; 969 break; 970 } 971 } 972 973 $value = $in_closing ? TRIGGER_VALUE_FALSE : TRIGGER_VALUE_TRUE; 974 $value_str = $in_closing ? _('CLOSING') : _('PROBLEM'); 975 $value_clock = $in_closing ? time() : $problem['clock']; 976 } 977 978 $is_acknowledged = ($problem['acknowledged'] == EVENT_ACKNOWLEDGED); 979 $cell_status = new CSpan($value_str); 980 981 // Add colors and blinking to span depending on configuration and trigger parameters. 982 addTriggerValueStyle($cell_status, $value, $value_clock, $is_acknowledged); 983 984 // Info. 985 $info_icons = []; 986 if ($problem['r_eventid'] != 0) { 987 if ($problem['correlationid'] != 0) { 988 $info_icons[] = makeInformationIcon( 989 array_key_exists($problem['correlationid'], $data['correlations']) 990 ? _s('Resolved by correlation rule "%1$s".', 991 $data['correlations'][$problem['correlationid']]['name'] 992 ) 993 : _('Resolved by correlation rule.') 994 ); 995 } 996 elseif ($problem['userid'] != 0) { 997 $info_icons[] = makeInformationIcon( 998 array_key_exists($problem['userid'], $data['users']) 999 ? _s('Resolved by user "%1$s".', getUserFullname($data['users'][$problem['userid']])) 1000 : _('Resolved by inaccessible user.') 1001 ); 1002 } 1003 } 1004 1005 if (array_key_exists('suppression_data', $problem) && $problem['suppression_data']) { 1006 $info_icons[] = makeSuppressedProblemIcon($problem['suppression_data']); 1007 } 1008 1009 $cell_info = ($this->data['filter']['compact_view'] && $this->data['filter']['show_suppressed'] 1010 && count($info_icons) > 1) 1011 ? (new CSpan( 1012 (new CButton(null)) 1013 ->addClass(ZBX_STYLE_ICON_WZRD_ACTION) 1014 ->addStyle('margin-left: -3px;') 1015 ->setHint((new CDiv($info_icons))->addClass(ZBX_STYLE_REL_CONTAINER)) 1016 ))->addClass(ZBX_STYLE_REL_CONTAINER) 1017 : makeInformationList($info_icons); 1018 1019 $description = array_key_exists($trigger['triggerid'], $dependencies) 1020 ? makeTriggerDependencies($dependencies[$trigger['triggerid']]) 1021 : []; 1022 $description[] = (new CLinkAction($problem['name'])) 1023 ->addClass(ZBX_STYLE_WORDWRAP) 1024 ->setMenuPopup(CMenuPopupHelper::getTrigger($trigger['triggerid'], $problem['eventid'])); 1025 1026 $opdata = null; 1027 1028 if ($show_opdata != OPERATIONAL_DATA_SHOW_NONE) { 1029 if ($trigger['opdata'] === '') { 1030 if ($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) { 1031 $opdata = (new CCol(self::getLatestValues($trigger['items'])))->addClass('latest-values'); 1032 } 1033 } 1034 else { 1035 $opdata = (new CSpan(CMacrosResolverHelper::resolveTriggerOpdata( 1036 [ 1037 'triggerid' => $trigger['triggerid'], 1038 'expression' => $trigger['expression'], 1039 'opdata' => $trigger['opdata'], 1040 'clock' => ($problem['r_eventid'] != 0) ? $problem['r_clock'] : $problem['clock'], 1041 'ns' => ($problem['r_eventid'] != 0) ? $problem['r_ns'] : $problem['ns'] 1042 ], 1043 [ 1044 'events' => true, 1045 'html' => true 1046 ] 1047 ))) 1048 ->addClass('opdata') 1049 ->addClass(ZBX_STYLE_WORDWRAP); 1050 1051 if ($show_opdata == OPERATIONAL_DATA_SHOW_WITH_PROBLEM) { 1052 $description[] = ' ('; 1053 $description[] = $opdata; 1054 $description[] = ')'; 1055 } 1056 } 1057 } 1058 1059 $description[] = ($problem['comments'] !== '') ? makeDescriptionIcon($problem['comments']) : null; 1060 1061 if ($this->data['filter']['details'] == 1) { 1062 $description[] = BR(); 1063 1064 if ($trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) { 1065 $description[] = [_('Problem'), ': ', (new CDiv($trigger['expression_html']))->addClass(ZBX_STYLE_WORDWRAP), BR()]; 1066 $description[] = [_('Recovery'), ': ', (new CDiv($trigger['recovery_expression_html']))->addClass(ZBX_STYLE_WORDWRAP)]; 1067 } 1068 else { 1069 $description[] = (new CDiv($trigger['expression_html']))->addClass(ZBX_STYLE_WORDWRAP); 1070 } 1071 } 1072 1073 if ($show_timeline) { 1074 if ($last_clock != 0) { 1075 self::addTimelineBreakpoint($table, $last_clock, $problem['clock'], $this->data['sortorder']); 1076 } 1077 $last_clock = $problem['clock']; 1078 1079 $row = [ 1080 $cell_clock->addClass(ZBX_STYLE_TIMELINE_DATE), 1081 (new CCol()) 1082 ->addClass(ZBX_STYLE_TIMELINE_AXIS) 1083 ->addClass(ZBX_STYLE_TIMELINE_DOT), 1084 (new CCol())->addClass(ZBX_STYLE_TIMELINE_TD) 1085 ]; 1086 } 1087 else { 1088 $row = [ 1089 $cell_clock 1090 ->addClass(ZBX_STYLE_NOWRAP) 1091 ->addClass(ZBX_STYLE_RIGHT) 1092 ]; 1093 } 1094 1095 // Create acknowledge link. 1096 $problem_update_link = ($allowed['add_comments'] || $allowed['change_severity'] 1097 || $allowed['acknowledge'] || $can_be_closed) 1098 ? (new CLink($is_acknowledged ? _('Yes') : _('No'))) 1099 ->addClass($is_acknowledged ? ZBX_STYLE_GREEN : ZBX_STYLE_RED) 1100 ->addClass(ZBX_STYLE_LINK_ALT) 1101 ->onClick('acknowledgePopUp('.json_encode(['eventids' => [$problem['eventid']]]).', this);') 1102 : (new CSpan($is_acknowledged ? _('Yes') : _('No')))->addClass( 1103 $is_acknowledged ? ZBX_STYLE_GREEN : ZBX_STYLE_RED 1104 ); 1105 1106 // Add table row. 1107 $table->addRow(array_merge($row, [ 1108 new CCheckBox('eventids['.$problem['eventid'].']', $problem['eventid']), 1109 getSeverityCell($problem['severity'], null, $value == TRIGGER_VALUE_FALSE), 1110 $show_recovery_data ? $cell_r_clock : null, 1111 $show_recovery_data ? $cell_status : null, 1112 $cell_info, 1113 $this->data['filter']['compact_view'] 1114 ? (new CDiv($triggers_hosts[$trigger['triggerid']]))->addClass('action-container') 1115 : $triggers_hosts[$trigger['triggerid']], 1116 $this->data['filter']['compact_view'] 1117 ? (new CDiv($description))->addClass('action-container') 1118 : $description, 1119 ($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) ? $opdata : null, 1120 ($problem['r_eventid'] != 0) 1121 ? zbx_date2age($problem['clock'], $problem['r_clock']) 1122 : zbx_date2age($problem['clock']), 1123 $problem_update_link, 1124 makeEventActionsIcons($problem['eventid'], $data['actions'], $data['users']), 1125 $this->data['filter']['show_tags'] ? $tags[$problem['eventid']] : null 1126 ]), ($this->data['filter']['highlight_row'] && $value == TRIGGER_VALUE_TRUE) 1127 ? getSeverityFlhStyle($problem['severity']) 1128 : null 1129 ); 1130 } 1131 1132 $footer = new CActionButtonList('action', 'eventids', [ 1133 'popup.acknowledge.edit' => [ 1134 'name' => _('Mass update'), 1135 'disabled' => !($allowed['add_comments'] || $allowed['change_severity'] || $allowed['acknowledge'] 1136 || $allowed['close'] 1137 ) 1138 ] 1139 ], 'problem'); 1140 1141 return $this->getOutput($form->addItem([$table, $paging, $footer]), true, $this->data); 1142 } 1143 1144 /* 1145 * Search limit performs +1 selection to know if limit was exceeded, this will assure that csv has 1146 * "search_limit" records at most. 1147 */ 1148 array_splice($data['problems'], CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT)); 1149 1150 $csv = []; 1151 1152 $csv[] = array_filter([ 1153 _('Severity'), 1154 _('Time'), 1155 _('Recovery time'), 1156 _('Status'), 1157 _('Host'), 1158 _('Problem'), 1159 ($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) ? _('Operational data') : null, 1160 _('Duration'), 1161 _('Ack'), 1162 _('Actions'), 1163 _('Tags') 1164 ]); 1165 1166 $tags = makeTags($data['problems'], false); 1167 1168 foreach ($data['problems'] as $problem) { 1169 $trigger = $data['triggers'][$problem['objectid']]; 1170 1171 if ($problem['r_eventid'] != 0) { 1172 $value_str = _('RESOLVED'); 1173 } 1174 else { 1175 $in_closing = false; 1176 1177 foreach ($problem['acknowledges'] as $acknowledge) { 1178 if (($acknowledge['action'] & ZBX_PROBLEM_UPDATE_CLOSE) == ZBX_PROBLEM_UPDATE_CLOSE) { 1179 $in_closing = true; 1180 break; 1181 } 1182 } 1183 1184 $value_str = $in_closing ? _('CLOSING') : _('PROBLEM'); 1185 } 1186 1187 $hosts = []; 1188 foreach ($triggers_hosts[$trigger['triggerid']] as $trigger_host) { 1189 $hosts[] = $trigger_host['name']; 1190 } 1191 1192 // operational data 1193 $opdata = null; 1194 if ($show_opdata != OPERATIONAL_DATA_SHOW_NONE) { 1195 if ($trigger['opdata'] === '') { 1196 if ($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) { 1197 $opdata = self::getLatestValues($trigger['items'], false); 1198 } 1199 } 1200 else { 1201 $opdata = CMacrosResolverHelper::resolveTriggerOpdata( 1202 [ 1203 'triggerid' => $trigger['triggerid'], 1204 'expression' => $trigger['expression'], 1205 'opdata' => $trigger['opdata'], 1206 'clock' => ($problem['r_eventid'] != 0) ? $problem['r_clock'] : $problem['clock'], 1207 'ns' => ($problem['r_eventid'] != 0) ? $problem['r_ns'] : $problem['ns'] 1208 ], 1209 ['events' => true] 1210 ); 1211 } 1212 } 1213 1214 $actions_performed = []; 1215 if ($data['actions']['messages'][$problem['eventid']]['count'] > 0) { 1216 $actions_performed[] = _('Messages'). 1217 ' ('.$data['actions']['messages'][$problem['eventid']]['count'].')'; 1218 } 1219 if ($data['actions']['severities'][$problem['eventid']]['count'] > 0) { 1220 $actions_performed[] = _('Severity changes'); 1221 } 1222 if ($data['actions']['actions'][$problem['eventid']]['count'] > 0) { 1223 $actions_performed[] = _('Actions').' ('.$data['actions']['actions'][$problem['eventid']]['count'].')'; 1224 } 1225 1226 $row = []; 1227 1228 $row[] = getSeverityName($problem['severity']); 1229 $row[] = zbx_date2str(DATE_TIME_FORMAT_SECONDS, $problem['clock']); 1230 $row[] = ($problem['r_eventid'] != 0) ? zbx_date2str(DATE_TIME_FORMAT_SECONDS, $problem['r_clock']) : ''; 1231 $row[] = $value_str; 1232 $row[] = implode(', ', $hosts); 1233 $row[] = ($show_opdata == OPERATIONAL_DATA_SHOW_WITH_PROBLEM && $trigger['opdata'] !== '') 1234 ? $problem['name'].' ('.$opdata.')' 1235 : $problem['name']; 1236 1237 if ($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) { 1238 $row[] = $opdata; 1239 } 1240 1241 $row[] = ($problem['r_eventid'] != 0) 1242 ? zbx_date2age($problem['clock'], $problem['r_clock']) 1243 : zbx_date2age($problem['clock']); 1244 $row[] = ($problem['acknowledged'] == EVENT_ACKNOWLEDGED) ? _('Yes') : _('No'); 1245 $row[] = implode(', ', $actions_performed); 1246 $row[] = implode(', ', $tags[$problem['eventid']]); 1247 1248 $csv[] = $row; 1249 } 1250 1251 return zbx_toCSV($csv); 1252 } 1253 1254 /** 1255 * Get item latest values. 1256 * 1257 * @static 1258 * 1259 * @param array $items An array of trigger items. 1260 * @param bool $html 1261 * 1262 * @return array|string 1263 */ 1264 public static function getLatestValues(array $items, bool $html = true) { 1265 $latest_values = []; 1266 1267 $items = zbx_toHash($items, 'itemid'); 1268 $history_values = Manager::History()->getLastValues($items, 1, timeUnitToSeconds(CSettingsHelper::get( 1269 CSettingsHelper::HISTORY_PERIOD 1270 ))); 1271 1272 if ($html) { 1273 $hint_table = (new CTable())->addClass('list-table'); 1274 } 1275 1276 foreach ($items as $itemid => $item) { 1277 if (array_key_exists($itemid, $history_values)) { 1278 $last_value = reset($history_values[$itemid]); 1279 $last_value['value'] = formatHistoryValue(str_replace(["\r\n", "\n"], [" "], $last_value['value']), 1280 $item 1281 ); 1282 } 1283 else { 1284 $last_value = [ 1285 'itemid' => null, 1286 'clock' => null, 1287 'value' => UNRESOLVED_MACRO_STRING, 1288 'ns' => null 1289 ]; 1290 } 1291 1292 if ($html) { 1293 $hint_table->addRow([ 1294 new CCol($item['name_expanded']), 1295 new CCol( 1296 ($last_value['clock'] !== null) 1297 ? zbx_date2str(DATE_TIME_FORMAT_SECONDS, $last_value['clock']) 1298 : UNRESOLVED_MACRO_STRING 1299 ), 1300 new CCol($last_value['value']), 1301 new CCol( 1302 ($item['value_type'] == ITEM_VALUE_TYPE_FLOAT || $item['value_type'] == ITEM_VALUE_TYPE_UINT64) 1303 ? (CWebUser::checkAccess(CRoleHelper::UI_MONITORING_LATEST_DATA) 1304 ? new CLink(_('Graph'), (new CUrl('history.php')) 1305 ->setArgument('action', HISTORY_GRAPH) 1306 ->setArgument('itemids[]', $itemid) 1307 ->getUrl() 1308 ) 1309 : _('Graph')) 1310 : (CWebUser::checkAccess(CRoleHelper::UI_MONITORING_LATEST_DATA) 1311 ? new CLink(_('History'), (new CUrl('history.php')) 1312 ->setArgument('action', HISTORY_VALUES) 1313 ->setArgument('itemids[]', $itemid) 1314 ->getUrl() 1315 ) 1316 : _('History')) 1317 ) 1318 ]); 1319 1320 $latest_values[] = (new CLinkAction($last_value['value'])) 1321 ->addClass('hint-item') 1322 ->setAttribute('data-hintbox', '1'); 1323 $latest_values[] = ', '; 1324 } 1325 else { 1326 $latest_values[] = $last_value['value']; 1327 } 1328 } 1329 1330 if ($html) { 1331 array_pop($latest_values); 1332 array_unshift($latest_values, (new CDiv()) 1333 ->addClass('main-hint') 1334 ->setHint($hint_table) 1335 ); 1336 1337 return $latest_values; 1338 } 1339 1340 return implode(', ', $latest_values); 1341 } 1342} 1343