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