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
22require_once dirname(__FILE__).'/graphs.inc.php';
23require_once dirname(__FILE__).'/maps.inc.php';
24require_once dirname(__FILE__).'/users.inc.php';
25
26/**
27 * @param array  $filter
28 * @param array  $filter['groupids']           (optional)
29 * @param array  $filter['exclude_groupids']   (optional)
30 * @param array  $filter['hostids']            (optional)
31 * @param string $filter['problem']            (optional)
32 * @param array  $filter['severities']         (optional)
33 * @param int    $filter['show_suppressed']    (optional)
34 * @param int    $filter['hide_empty_groups']  (optional)
35 * @param int    $filter['ext_ack']            (optional)
36 * @param int    $filter['show_opdata']        (optional)
37 *
38 * @return array
39 */
40function getSystemStatusData(array $filter) {
41	$filter_groupids = (array_key_exists('groupids', $filter) && $filter['groupids']) ? $filter['groupids'] : null;
42	$filter_hostids = (array_key_exists('hostids', $filter) && $filter['hostids']) ? $filter['hostids'] : null;
43	$filter_severities = (array_key_exists('severities', $filter) && $filter['severities'])
44		? $filter['severities']
45		: range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1);
46	$filter_ext_ack = array_key_exists('ext_ack', $filter)
47		? $filter['ext_ack']
48		: EXTACK_OPTION_ALL;
49	$filter_evaltype = array_key_exists('evaltype', $filter) ? $filter['evaltype'] : TAG_EVAL_TYPE_AND_OR;
50	$filter_tags = array_key_exists('tags', $filter) ? $filter['tags'] : [];
51	$show_opdata = array_key_exists('show_opdata', $filter) && $filter['show_opdata'] != OPERATIONAL_DATA_SHOW_NONE;
52
53	if (array_key_exists('exclude_groupids', $filter) && $filter['exclude_groupids']) {
54		if ($filter_hostids === null) {
55			// Get all groups if no selected groups defined.
56			if ($filter_groupids === null) {
57				$filter_groupids = array_keys(API::HostGroup()->get([
58					'output' => [],
59					'real_hosts' => true,
60					'preservekeys' => true
61				]));
62			}
63
64			$filter_groupids = array_diff($filter_groupids, $filter['exclude_groupids']);
65
66			// Get available hosts.
67			$filter_hostids = array_keys(API::Host()->get([
68				'output' => [],
69				'groupids' => $filter_groupids,
70				'preservekeys' => true
71			]));
72		}
73
74		$exclude_hostids = array_keys(API::Host()->get([
75			'output' => [],
76			'groupids' => $filter['exclude_groupids'],
77			'preservekeys' => true
78		]));
79
80		$filter_hostids = array_diff($filter_hostids, $exclude_hostids);
81	}
82
83	$data = [
84		'groups' => API::HostGroup()->get([
85			'output' => ['groupid', 'name'],
86			'groupids' => $filter_groupids,
87			'hostids' => $filter_hostids,
88			'monitored_hosts' => true,
89			'preservekeys' => true
90		]),
91		'triggers' => [],
92		'actions' => [],
93		'stats' => [],
94		'allowed' => [
95			'ui_problems' => CWebUser::checkAccess(CRoleHelper::UI_MONITORING_PROBLEMS),
96			'add_comments' => CWebUser::checkAccess(CRoleHelper::ACTIONS_ADD_PROBLEM_COMMENTS),
97			'change_severity' => CWebUser::checkAccess(CRoleHelper::ACTIONS_CHANGE_SEVERITY),
98			'acknowledge' => CWebUser::checkAccess(CRoleHelper::ACTIONS_ACKNOWLEDGE_PROBLEMS),
99			'close' => CWebUser::checkAccess(CRoleHelper::ACTIONS_CLOSE_PROBLEMS)
100		]
101	];
102
103	CArrayHelper::sort($data['groups'], [['field' => 'name', 'order' => ZBX_SORT_UP]]);
104
105	$default_stats = [];
106
107	for ($severity = TRIGGER_SEVERITY_COUNT - 1; $severity >= TRIGGER_SEVERITY_NOT_CLASSIFIED; $severity--) {
108		if (in_array($severity, $filter_severities)) {
109			$default_stats[$severity] = ['count' => 0, 'problems' => [], 'count_unack' => 0, 'problems_unack' => []];
110		}
111	}
112
113	$data['stats'] = $default_stats;
114
115	foreach ($data['groups'] as &$group) {
116		$group['stats'] = $default_stats;
117		$group['has_problems'] = false;
118	}
119	unset($group);
120
121	$options = [
122		'output' => ['eventid', 'r_eventid', 'objectid', 'clock', 'ns', 'name', 'acknowledged', 'severity'],
123		'selectAcknowledges' => ['action'],
124		'groupids' => array_keys($data['groups']),
125		'hostids' => $filter_hostids,
126		'evaltype' => $filter_evaltype,
127		'tags' => $filter_tags,
128		'source' => EVENT_SOURCE_TRIGGERS,
129		'object' => EVENT_OBJECT_TRIGGER,
130		'suppressed' => false,
131		'sortfield' => ['eventid'],
132		'sortorder' => ZBX_SORT_DOWN,
133		'preservekeys' => true
134	];
135
136	if (array_key_exists('severities', $filter)) {
137		$filter_severities = implode(',', $filter['severities']);
138		$all_severities = implode(',', range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1));
139
140		if ($filter_severities !== '' && $filter_severities !== $all_severities) {
141			$options['severities'] = $filter['severities'];
142		}
143	}
144
145	if (array_key_exists('show_suppressed', $filter) && $filter['show_suppressed']) {
146		unset($options['suppressed']);
147		$options['selectSuppressionData'] = ['maintenanceid', 'suppress_until'];
148	}
149
150	if ($filter_ext_ack == EXTACK_OPTION_UNACK) {
151		$options['acknowledged'] = false;
152	}
153
154	if (array_key_exists('problem', $filter) && $filter['problem'] !== '') {
155		$options['search'] = ['name' => $filter['problem']];
156	}
157
158	$problems = API::Problem()->get($options);
159	if ($problems) {
160		$triggerids = [];
161
162		foreach ($problems as $problem) {
163			$triggerids[$problem['objectid']] = true;
164		}
165
166		$options = [
167			'output' => ['priority', 'manual_close'],
168			'selectGroups' => ['groupid'],
169			'selectHosts' => ['name'],
170			'triggerids' => array_keys($triggerids),
171			'monitored' => true,
172			'skipDependent' => true,
173			'preservekeys' => true
174		];
175
176		if ($show_opdata) {
177			$options['selectFunctions'] = ['itemid'];
178			$options['output'] = array_merge($options['output'],
179				['expression', 'recovery_mode', 'recovery_expression', 'opdata']
180			);
181		}
182
183		$data['triggers'] = API::Trigger()->get($options);
184
185		if ($show_opdata && $data['triggers']) {
186			$items = API::Item()->get([
187				'output' => ['itemid', 'hostid', 'name', 'key_', 'value_type', 'units'],
188				'selectValueMap' => ['mappings'],
189				'triggerids' => array_keys($data['triggers']),
190				'webitems' => true,
191				'preservekeys' => true
192			]);
193
194			foreach ($data['triggers'] as &$trigger) {
195				foreach ($trigger['functions'] as $function) {
196					$trigger['items'][] = $items[$function['itemid']];
197				}
198				unset($trigger['functions']);
199			}
200			unset($trigger);
201		}
202
203		foreach ($data['triggers'] as &$trigger) {
204			CArrayHelper::sort($trigger['hosts'], [['field' => 'name', 'order' => ZBX_SORT_UP]]);
205		}
206		unset($trigger);
207
208		foreach ($problems as $eventid => $problem) {
209			if (!array_key_exists($problem['objectid'], $data['triggers'])) {
210				unset($problems[$eventid]);
211			}
212		}
213
214		$visible_problems = [];
215
216		foreach ($problems as $eventid => $problem) {
217			$trigger = $data['triggers'][$problem['objectid']];
218
219			$data['stats'][$problem['severity']]['count']++;
220			if ($problem['acknowledged'] == EVENT_NOT_ACKNOWLEDGED) {
221				$data['stats'][$problem['severity']]['count_unack']++;
222			}
223
224			// groups
225			foreach ($trigger['groups'] as $trigger_group) {
226				if (!array_key_exists($trigger_group['groupid'], $data['groups'])) {
227					continue;
228				}
229
230				$group = &$data['groups'][$trigger_group['groupid']];
231
232				if (in_array($filter_ext_ack, [EXTACK_OPTION_ALL, EXTACK_OPTION_BOTH])) {
233					if ($group['stats'][$problem['severity']]['count'] < ZBX_WIDGET_ROWS) {
234						$group['stats'][$problem['severity']]['problems'][] = $problem;
235						$visible_problems[$eventid] = ['eventid' => $eventid];
236					}
237
238					$group['stats'][$problem['severity']]['count']++;
239				}
240
241				if (in_array($filter_ext_ack, [EXTACK_OPTION_UNACK, EXTACK_OPTION_BOTH])
242						&& $problem['acknowledged'] == EVENT_NOT_ACKNOWLEDGED) {
243					if ($group['stats'][$problem['severity']]['count_unack'] < ZBX_WIDGET_ROWS) {
244						$group['stats'][$problem['severity']]['problems_unack'][] = $problem;
245						$visible_problems[$eventid] = ['eventid' => $eventid];
246					}
247
248					$group['stats'][$problem['severity']]['count_unack']++;
249				}
250
251				$group['has_problems'] = true;
252			}
253			unset($group);
254		}
255
256		// actions & tags
257		$problems_data = API::Problem()->get([
258			'output' => ['eventid', 'r_eventid', 'clock', 'objectid', 'severity'],
259			'eventids' => array_keys($visible_problems),
260			'selectAcknowledges' => ['userid', 'clock', 'message', 'action', 'old_severity', 'new_severity'],
261			'selectTags' => ['tag', 'value'],
262			'preservekeys' => true
263		]);
264
265		// Remove problems that were resolved between requests or set tags.
266		foreach ($data['groups'] as $groupid => &$group) {
267			foreach ($group['stats'] as $severity => &$stat) {
268				foreach (['problems', 'problems_unack'] as $key) {
269					foreach ($stat[$key] as $event_no => &$problem) {
270						if (array_key_exists($problem['eventid'], $problems_data)) {
271							$problem['tags'] = $problems_data[$problem['eventid']]['tags'];
272						}
273						else {
274							if ($key === 'problems') {
275								$data['groups'][$groupid]['stats'][$severity]['count']--;
276							}
277							else {
278								$data['groups'][$groupid]['stats'][$severity]['count_unack']--;
279							}
280							unset($data['groups'][$groupid]['stats'][$severity][$key][$event_no]);
281						}
282					}
283					unset($problem);
284				}
285			}
286			unset($stat);
287		}
288		unset($group);
289
290		// actions
291		// Possible performance improvement: one API call may be saved, if r_clock for problem will be used.
292		$actions = getEventsActionsIconsData($problems_data, $data['triggers']);
293		$data['actions'] = [
294			'all_actions' => $actions['data'],
295			'users' => API::User()->get([
296				'output' => ['username', 'name', 'surname'],
297				'userids' => array_keys($actions['userids']),
298				'preservekeys' => true
299			])
300		];
301
302		if (array_key_exists('show_opdata', $filter) && $filter['show_opdata'] != OPERATIONAL_DATA_SHOW_NONE) {
303			$maked_data = CScreenProblem::makeData(
304				['problems' => $problems_data, 'triggers' => $data['triggers']],
305				['show' => 0, 'details' => 0, 'show_opdata' => $filter['show_opdata']]
306			);
307			$data['triggers'] = $maked_data['triggers'];
308		}
309	}
310
311	return $data;
312}
313
314/**
315 * @param array  $data
316 * @param array  $data['groups']
317 * @param string $data['groups'][]['groupid']
318 * @param string $data['groups'][]['name']
319 * @param bool   $data['groups'][]['has_problems']
320 * @param array  $data['groups'][]['stats']
321 * @param int    $data['groups'][]['stats'][]['count']
322 * @param array  $data['groups'][]['stats'][]['problems']
323 * @param int    $data['groups'][]['stats'][]['count_unack']
324 * @param array  $data['groups'][]['stats'][]['problems_unack']
325 *
326 * @return array
327 */
328function getSystemStatusTotals(array $data) {
329	$groups_totals = [
330		0 => [
331			'groupid' => 0,
332			'stats' => []
333		]
334	];
335
336	foreach ($data['stats'] as $severity => $value) {
337		$groups_totals[0]['stats'][$severity] = [
338			'count' => $value['count'],
339			'problems' => [],
340			'count_unack' => $value['count_unack'],
341			'problems_unack' => []
342		];
343	}
344
345	foreach ($data['groups'] as $group) {
346		foreach ($group['stats'] as $severity => $stat) {
347			foreach ($stat['problems'] as $problem) {
348				$groups_totals[0]['stats'][$severity]['problems'][$problem['eventid']] = $problem;
349			}
350			foreach ($stat['problems_unack'] as $problem) {
351				$groups_totals[0]['stats'][$severity]['problems_unack'][$problem['eventid']] = $problem;
352			}
353		}
354	}
355
356	return $groups_totals;
357}
358
359/**
360 * @param array $data
361 * @param array $data['data']
362 * @param array $data['data']['groups']
363 * @param array $data['data']['groups'][]['stats']
364 * @param array $data['filter']
365 * @param array $data['filter']['severities']
366 * @param array $data['allowed']
367 * @param bool  $data['allowed']['ui_problems']
368 * @param bool  $data['allowed']['add_comments']
369 * @param bool  $data['allowed']['change_severity']
370 * @param bool  $data['allowed']['acknowledge']
371 * @param bool  $data['allowed']['close']
372 * @param bool  $hide_empty_groups
373 * @param CUrl  $groupurl
374 *
375 * @return CTableInfo
376 */
377function makeSeverityTable(array $data, $hide_empty_groups = false, CUrl $groupurl = null) {
378	$table = new CTableInfo();
379
380	foreach ($data['data']['groups'] as $group) {
381		if ($hide_empty_groups && !$group['has_problems']) {
382			// Skip row.
383			continue;
384		}
385
386		if ($data['allowed']['ui_problems']) {
387			$groupurl->setArgument('groupids', [$group['groupid']]);
388			$row = [new CLink($group['name'], $groupurl->getUrl())];
389		}
390		else {
391			$row = [$group['name']];
392		}
393
394		foreach ($group['stats'] as $severity => $stat) {
395			if ($data['filter']['severities'] && !in_array($severity, $data['filter']['severities'])) {
396				// Skip cell.
397				continue;
398			}
399
400			$row[] = getSeverityTableCell($severity, $data, $stat);
401		}
402
403		$table->addRow($row);
404	}
405
406	return $table;
407}
408
409/**
410 * @param array $data
411 * @param array $data['data']
412 * @param array $data['data']['groups']
413 * @param array $data['data']['groups'][]['stats']
414 * @param array $data['filter']
415 * @param array $data['filter']['severities']
416 * @param array $data['allowed']
417 * @param bool  $data['allowed']['ui_problems']
418 * @param bool  $data['allowed']['add_comments']
419 * @param bool  $data['allowed']['change_severity']
420 * @param bool  $data['allowed']['acknowledge']
421 * @param bool  $data['allowed']['close']
422 *
423 * @return CDiv
424 */
425function makeSeverityTotals(array $data) {
426	$table = new CDiv();
427
428	foreach ($data['data']['groups'] as $group) {
429		foreach ($group['stats'] as $severity => $stat) {
430			if ($data['filter']['severities'] && !in_array($severity, $data['filter']['severities'])) {
431				// Skip cell.
432				continue;
433			}
434			$table->addItem(getSeverityTableCell($severity, $data, $stat, true));
435		}
436	}
437
438	return $table;
439}
440
441/**
442 * @param int   $severity
443 * @param array $data
444 * @param array $data['data']
445 * @param array $data['data']['triggers']
446 * @param array $data['data']['actions']
447 * @param array $data['filter']
448 * @param array $data['filter']['ext_ack']
449 * @param array $data['severity_names']
450 * @param array $data['allowed']
451 * @param bool  $data['allowed']['ui_problems']
452 * @param bool  $data['allowed']['add_comments']
453 * @param bool  $data['allowed']['change_severity']
454 * @param bool  $data['allowed']['acknowledge']
455 * @param bool  $data['allowed']['close']
456 * @param array $stat
457 * @param int   $stat['count']
458 * @param array $stat['problems']
459 * @param int   $stat['count_unack']
460 * @param array $stat['problems_unack']
461 * @param bool  $is_total
462 *
463 * @return CCol|string
464 */
465function getSeverityTableCell($severity, array $data, array $stat, $is_total = false) {
466	if (!$is_total && $stat['count'] == 0 && $stat['count_unack'] == 0) {
467		return '';
468	}
469
470	$severity_name = $is_total ? ' '.getSeverityName($severity) : '';
471	$ext_ack = array_key_exists('ext_ack', $data['filter']) ? $data['filter']['ext_ack'] : EXTACK_OPTION_ALL;
472
473	$allTriggersNum = $stat['count'];
474	if ($allTriggersNum) {
475		$allTriggersNum = (new CLinkAction($allTriggersNum))
476			->setHint(makeProblemsPopup($stat['problems'], $data['data']['triggers'], $data['data']['actions'],
477				$data['filter'], $data['allowed']
478			));
479	}
480
481	$unackTriggersNum = $stat['count_unack'];
482	if ($unackTriggersNum) {
483		$unackTriggersNum = (new CLinkAction($unackTriggersNum))
484			->setHint(makeProblemsPopup($stat['problems_unack'], $data['data']['triggers'], $data['data']['actions'],
485				$data['filter'], $data['allowed']
486			));
487	}
488
489	switch ($ext_ack) {
490		case EXTACK_OPTION_ALL:
491			return getSeverityCell($severity, [
492				(new CSpan($allTriggersNum))->addClass(ZBX_STYLE_TOTALS_LIST_COUNT),
493				$severity_name
494			], false, $is_total);
495
496		case EXTACK_OPTION_UNACK:
497			return getSeverityCell($severity, [
498				(new CSpan($unackTriggersNum))->addClass(ZBX_STYLE_TOTALS_LIST_COUNT),
499				$severity_name
500			], false, $is_total);
501
502		case EXTACK_OPTION_BOTH:
503			return getSeverityCell($severity, [
504				(new CSpan([$unackTriggersNum, ' '._('of').' ', $allTriggersNum]))
505					->addClass(ZBX_STYLE_TOTALS_LIST_COUNT),
506				$severity_name
507			], false, $is_total);
508
509		default:
510			return '';
511	}
512}
513
514function make_status_of_zbx() {
515	if (CWebUser::getType() == USER_TYPE_SUPER_ADMIN) {
516		global $ZBX_SERVER, $ZBX_SERVER_PORT;
517
518		$server_details = $ZBX_SERVER.':'.$ZBX_SERVER_PORT;
519	}
520	else {
521		$server_details = '';
522	}
523
524	$table = (new CTableInfo())
525		->setHeader([_('Parameter'), _('Value'), _('Details')])
526		->setHeadingColumn(0);
527
528	$status = get_status();
529
530	$table
531		->addRow([_('Zabbix server is running'),
532			(new CSpan($status['is_running'] ? _('Yes') : _('No')))
533				->addClass($status['is_running'] ? ZBX_STYLE_GREEN : ZBX_STYLE_RED),
534			$server_details
535		])
536		->addRow([_('Number of hosts (enabled/disabled)'),
537			$status['has_status'] ? $status['hosts_count'] : '',
538			$status['has_status']
539				? [
540					(new CSpan($status['hosts_count_monitored']))->addClass(ZBX_STYLE_GREEN), ' / ',
541					(new CSpan($status['hosts_count_not_monitored']))->addClass(ZBX_STYLE_RED)
542				]
543				: ''
544		])
545		->addRow([_('Number of templates'),
546			$status['has_status'] ? $status['hosts_count_template'] : '', ''
547		]);
548
549	$title = (new CSpan(_('Number of items (enabled/disabled/not supported)')))
550		->setTitle(_('Only items assigned to enabled hosts are counted'));
551	$table->addRow([$title, $status['has_status'] ? $status['items_count'] : '',
552		$status['has_status']
553			? [
554				(new CSpan($status['items_count_monitored']))->addClass(ZBX_STYLE_GREEN), ' / ',
555				(new CSpan($status['items_count_disabled']))->addClass(ZBX_STYLE_RED), ' / ',
556				(new CSpan($status['items_count_not_supported']))->addClass(ZBX_STYLE_GREY)
557			]
558			: ''
559	]);
560	$title = (new CSpan(_('Number of triggers (enabled/disabled [problem/ok])')))
561		->setTitle(_('Only triggers assigned to enabled hosts and depending on enabled items are counted'));
562	$table->addRow([$title, $status['has_status'] ? $status['triggers_count'] : '',
563		$status['has_status']
564			? [
565				$status['triggers_count_enabled'], ' / ',
566				$status['triggers_count_disabled'], ' [',
567				(new CSpan($status['triggers_count_on']))->addClass(ZBX_STYLE_RED), ' / ',
568				(new CSpan($status['triggers_count_off']))->addClass(ZBX_STYLE_GREEN), ']'
569			]
570			: ''
571	]);
572	$table->addRow([_('Number of users (online)'), $status['has_status'] ? $status['users_count'] : '',
573		$status['has_status'] ? (new CSpan($status['users_online']))->addClass(ZBX_STYLE_GREEN) : ''
574	]);
575	if (CWebUser::getType() == USER_TYPE_SUPER_ADMIN) {
576		$table->addRow([_('Required server performance, new values per second'),
577			($status['has_status'] && array_key_exists('vps_total', $status)) ? round($status['vps_total'], 2) : '', ''
578		]);
579	}
580
581	// Check requirements.
582	if (CWebUser::getType() == USER_TYPE_SUPER_ADMIN) {
583		$setup = new CFrontendSetup();
584		$reqs = $setup->checkRequirements();
585		$reqs[] = $setup->checkSslFiles();
586
587		foreach ($reqs as $req) {
588			if ($req['result'] == CFrontendSetup::CHECK_FATAL) {
589				$table->addRow(
590					(new CRow([$req['name'], $req['current'], $req['error']]))->addClass(ZBX_STYLE_RED)
591				);
592			}
593		}
594
595		$db = DB::getDbBackend();
596
597		if (!$db->checkEncoding()) {
598			$table->addRow(
599				(new CRow((new CCol($db->getWarning()))->setAttribute('colspan', 3)))->addClass(ZBX_STYLE_RED)
600			);
601		}
602	}
603
604	// Warn if database history tables have not been upgraded.
605	global $DB;
606
607	if (!$DB['DOUBLE_IEEE754']) {
608		$table->addRow([
609			_('Database history tables upgraded'),
610			(new CSpan(_('No')))->addClass(ZBX_STYLE_RED),
611			''
612		]);
613	}
614
615	// Check DB version.
616	if (CWebUser::getType() == USER_TYPE_SUPER_ADMIN) {
617		$dbversion_status = CSettingsHelper::getGlobal(CSettingsHelper::DBVERSION_STATUS);
618
619		if ($dbversion_status !== '') {
620			$dbversion_status = json_decode($dbversion_status);
621
622			foreach ($dbversion_status as $dbversion) {
623				if ($dbversion->flag != DB_VERSION_SUPPORTED) {
624					switch ($dbversion->flag) {
625						case DB_VERSION_LOWER_THAN_MINIMUM:
626							$error = _s('Minimum required %1$s database version is %2$s.', $dbversion->database,
627								$dbversion->min_version
628							);
629							break;
630
631						case DB_VERSION_HIGHER_THAN_MAXIMUM:
632							$error = _s('Maximum required %1$s database version is %2$s.', $dbversion->database,
633								$dbversion->max_version
634							);
635							break;
636
637						case DB_VERSION_FAILED_TO_RETRIEVE:
638							$error = _('Unable to retrieve database version.');
639							$dbversion->current_version = '';
640							break;
641					}
642
643					$table->addRow(
644						(new CRow([$dbversion->database, $dbversion->current_version, $error]))->addClass(ZBX_STYLE_RED)
645					);
646				}
647			}
648		}
649	}
650
651	return $table;
652}
653
654/**
655 * Generate table for dashboard triggers popup.
656 *
657 * @param array  $problems
658 * @param string $problems[]['objectid']
659 * @param int    $problems[]['clock']
660 * @param int    $problems[]['ns']
661 * @param string $problems[]['r_eventid']
662 * @param string $problems[]['acknowledged']
663 * @param array  $problems[]['acknowledges']
664 * @param string $problems[]['acknowledges'][]['action']
665 * @param array  $problems[]['severity']
666 * @param array  $problems[]['suppression_data']
667 * @param array  $problems[]['tags']
668 * @param string $problems[]['tags'][]['tag']
669 * @param string $problems[]['tags'][]['value']
670 * @param array  $triggers
671 * @param string $triggers[<triggerid>]['expression']
672 * @param string $triggers[<triggerid>]['description']
673 * @param string $triggers[<triggerid>]['manual_close']
674 * @param array  $triggers[<triggerid>]['hosts']
675 * @param string $triggers[<triggerid>]['hosts'][]['name']
676 * @param string $triggers[<triggerid>]['opdata']
677 * @param array  $actions
678 * @param array  $filter
679 * @param array  $filter['show_suppressed']  (optional)
680 * @param array  $filter['show_timeline']    (optional)
681 * @param array  $filter['show_opdata']      (optional)
682 * @param array  $allowed
683 * @param bool   $allowed['ui_problems']
684 * @param bool   $allowed['add_comments']
685 * @param bool   $allowed['change_severity']
686 * @param bool   $allowed['acknowledge']
687 * @param bool   $allowed['close']
688 *
689 * @return CTableInfo
690 */
691function makeProblemsPopup(array $problems, array $triggers, array $actions, array $filter, array $allowed) {
692	$url_details = $allowed['ui_problems']
693		? (new CUrl('tr_events.php'))
694			->setArgument('triggerid', '')
695			->setArgument('eventid', '')
696		: null;
697
698	$header_time = new CColHeader([_('Time'), (new CSpan())->addClass(ZBX_STYLE_ARROW_DOWN)]);
699
700	$show_timeline = (array_key_exists('show_timeline', $filter) && $filter['show_timeline']);
701	$show_opdata = (array_key_exists('show_opdata', $filter)) ? $filter['show_opdata'] : OPERATIONAL_DATA_SHOW_NONE;
702
703	if ($show_timeline) {
704		$header = [
705			$header_time->addClass(ZBX_STYLE_RIGHT),
706			(new CColHeader())->addClass(ZBX_STYLE_TIMELINE_TH),
707			(new CColHeader())->addClass(ZBX_STYLE_TIMELINE_TH)
708		];
709	}
710	else {
711		$header = [$header_time];
712	}
713
714	$table = (new CTableInfo())
715		->setHeader(array_merge($header, [
716			_('Info'),
717			_('Host'),
718			_('Problem'),
719			($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) ? _('Operational data') : null,
720			_('Duration'),
721			_('Ack'),
722			_('Actions'),
723			_('Tags')
724		]));
725
726	$today = strtotime('today');
727	$last_clock = 0;
728
729	// Unset triggers, which missing in problems array.
730	if ($problems) {
731		$objectids = [];
732
733		foreach ($problems as $problem) {
734			$objectids[$problem['objectid']] = true;
735		}
736
737		$triggers = array_intersect_key($triggers, $objectids);
738	}
739
740	$triggers_hosts = getTriggersHostsList($triggers);
741	$triggers_hosts = makeTriggersHostsList($triggers_hosts);
742
743	$tags = makeTags($problems);
744
745	if (array_key_exists('show_suppressed', $filter) && $filter['show_suppressed']) {
746		CScreenProblem::addMaintenanceNames($problems);
747	}
748
749	foreach ($problems as $problem) {
750		$trigger = $triggers[$problem['objectid']];
751
752		$cell_clock = ($problem['clock'] >= $today)
753			? zbx_date2str(TIME_FORMAT_SECONDS, $problem['clock'])
754			: zbx_date2str(DATE_TIME_FORMAT_SECONDS, $problem['clock']);
755
756		if ($url_details !== null) {
757			$url_details
758				->setArgument('triggerid', $problem['objectid'])
759				->setArgument('eventid', $problem['eventid']);
760			$cell_clock = new CCol(new CLink($cell_clock, $url_details));
761		}
762		else {
763			$cell_clock = new CCol($cell_clock);
764		}
765
766		if ($show_timeline) {
767			if ($last_clock != 0) {
768				CScreenProblem::addTimelineBreakpoint($table, $last_clock, $problem['clock'], ZBX_SORT_DOWN);
769			}
770			$last_clock = $problem['clock'];
771
772			$row = [
773				$cell_clock->addClass(ZBX_STYLE_TIMELINE_DATE),
774				(new CCol())
775					->addClass(ZBX_STYLE_TIMELINE_AXIS)
776					->addClass(ZBX_STYLE_TIMELINE_DOT),
777				(new CCol())->addClass(ZBX_STYLE_TIMELINE_TD)
778			];
779		}
780		else {
781			$row = [
782				$cell_clock
783					->addClass(ZBX_STYLE_NOWRAP)
784					->addClass(ZBX_STYLE_RIGHT)
785			];
786		}
787
788		$info_icons = [];
789		if (array_key_exists('suppression_data', $problem) && $problem['suppression_data']) {
790			$info_icons[] = makeSuppressedProblemIcon($problem['suppression_data']);
791		}
792
793		// operational data
794		$opdata = null;
795		if ($show_opdata != OPERATIONAL_DATA_SHOW_NONE) {
796
797			if ($trigger['opdata'] === '') {
798				if ($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) {
799					$opdata = (new CCol(CScreenProblem::getLatestValues($trigger['items'])))->addClass('latest-values');
800				}
801			}
802			else {
803				$opdata = CMacrosResolverHelper::resolveTriggerOpdata(
804					[
805						'triggerid' => $trigger['triggerid'],
806						'expression' => $trigger['expression'],
807						'opdata' => $trigger['opdata'],
808						'clock' => $problem['clock'],
809						'ns' => $problem['ns']
810					],
811					[
812						'events' => true,
813						'html' => true
814					]
815				);
816
817				if ($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) {
818					$opdata = (new CCol($opdata))
819						->addClass('opdata')
820						->addClass(ZBX_STYLE_WORDWRAP);
821				}
822			}
823		}
824
825		$can_be_closed = ($trigger['manual_close'] == ZBX_TRIGGER_MANUAL_CLOSE_ALLOWED && $allowed['close']);
826
827		if ($problem['r_eventid'] != 0) {
828			$can_be_closed = false;
829		}
830		else {
831			foreach ($problem['acknowledges'] as $acknowledge) {
832				if (($acknowledge['action'] & ZBX_PROBLEM_UPDATE_CLOSE) == ZBX_PROBLEM_UPDATE_CLOSE) {
833					$can_be_closed = false;
834					break;
835				}
836			}
837		}
838
839		// Create acknowledge link.
840		$is_acknowledged = ($problem['acknowledged'] == EVENT_ACKNOWLEDGED);
841		$problem_update_link = ($allowed['add_comments'] || $allowed['change_severity'] || $allowed['acknowledge']
842				|| $can_be_closed)
843			? (new CLink($is_acknowledged ? _('Yes') : _('No')))
844				->addClass($is_acknowledged ? ZBX_STYLE_GREEN : ZBX_STYLE_RED)
845				->addClass(ZBX_STYLE_LINK_ALT)
846				->onClick('acknowledgePopUp('.json_encode(['eventids' => [$problem['eventid']]]).', this);')
847			: (new CSpan($is_acknowledged ? _('Yes') : _('No')))->addClass(
848				$is_acknowledged ? ZBX_STYLE_GREEN : ZBX_STYLE_RED
849			);
850
851		$table->addRow(array_merge($row, [
852			makeInformationList($info_icons),
853			$triggers_hosts[$trigger['triggerid']],
854			getSeverityCell($problem['severity'],
855				(($show_opdata == OPERATIONAL_DATA_SHOW_WITH_PROBLEM && $opdata)
856					? [$problem['name'], ' (', $opdata, ')']
857					: $problem['name']
858				)
859			),
860			($show_opdata == OPERATIONAL_DATA_SHOW_SEPARATELY) ? $opdata : null,
861			zbx_date2age($problem['clock']),
862			$problem_update_link,
863			makeEventActionsIcons($problem['eventid'], $actions['all_actions'], $actions['users']),
864			$tags[$problem['eventid']]
865		]));
866	}
867
868	return $table;
869}
870