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 * Returns the names of supported event sources.
24 *
25 * If the $source parameter is passed, returns the name of the specific source, otherwise - returns an array of all
26 * supported sources.
27 *
28 * @param int $source
29 *
30 * @return array|string
31 */
32function eventSource($source = null) {
33	$sources = [
34		EVENT_SOURCE_TRIGGERS => _('trigger'),
35		EVENT_SOURCE_DISCOVERY => _('discovery'),
36		EVENT_SOURCE_AUTOREGISTRATION => _('autoregistration'),
37		EVENT_SOURCE_INTERNAL => _x('internal', 'event source')
38	];
39
40	if ($source === null) {
41		return $sources;
42	}
43
44	return array_key_exists($source, $sources) ?  $sources[$source] : _('Unknown');
45}
46
47/**
48 * Returns the names of supported event objects.
49 *
50 * If the $source parameter is passed, returns the name of the specific object, otherwise - returns an array of all
51 * supported objects.
52 *
53 * @param int $object
54 *
55 * @return array|string
56 */
57function eventObject($object = null) {
58	$objects = [
59		EVENT_OBJECT_TRIGGER => _('trigger'),
60		EVENT_OBJECT_DHOST => _('discovered host'),
61		EVENT_OBJECT_DSERVICE => _('discovered service'),
62		EVENT_OBJECT_AUTOREGHOST => _('autoregistered host'),
63		EVENT_OBJECT_ITEM => _('item'),
64		EVENT_OBJECT_LLDRULE => _('low-level discovery rule')
65	];
66
67	if ($object === null) {
68		return $objects;
69	}
70	elseif (isset($objects[$object])) {
71		return $objects[$object];
72	}
73	else {
74		return _('Unknown');
75	}
76}
77
78/**
79 * Returns all supported event source-object pairs.
80 *
81 * @return array
82 */
83function eventSourceObjects() {
84	return [
85		['source' => EVENT_SOURCE_TRIGGERS, 'object' => EVENT_OBJECT_TRIGGER],
86		['source' => EVENT_SOURCE_DISCOVERY, 'object' => EVENT_OBJECT_DHOST],
87		['source' => EVENT_SOURCE_DISCOVERY, 'object' => EVENT_OBJECT_DSERVICE],
88		['source' => EVENT_SOURCE_AUTOREGISTRATION, 'object' => EVENT_OBJECT_AUTOREGHOST],
89		['source' => EVENT_SOURCE_INTERNAL, 'object' => EVENT_OBJECT_TRIGGER],
90		['source' => EVENT_SOURCE_INTERNAL, 'object' => EVENT_OBJECT_ITEM],
91		['source' => EVENT_SOURCE_INTERNAL, 'object' => EVENT_OBJECT_LLDRULE]
92	];
93}
94
95function get_events_unacknowledged($db_element, $value_trigger = null, $value_event = null, $ack = false) {
96	$elements = ['hosts' => [], 'hosts_groups' => [], 'triggers' => []];
97	get_map_elements($db_element, $elements);
98
99	if (empty($elements['hosts_groups']) && empty($elements['hosts']) && empty($elements['triggers'])) {
100		return 0;
101	}
102
103	$config = select_config();
104	$options = [
105		'output' => ['triggerid'],
106		'monitored' => 1,
107		'skipDependent' => 1,
108		'limit' => $config['search_limit'] + 1
109	];
110	if (!is_null($value_trigger)) {
111		$options['filter'] = ['value' => $value_trigger];
112	}
113	if (!empty($elements['hosts_groups'])) {
114		$options['groupids'] = array_unique($elements['hosts_groups']);
115	}
116	if (!empty($elements['hosts'])) {
117		$options['hostids'] = array_unique($elements['hosts']);
118	}
119	if (!empty($elements['triggers'])) {
120		$options['triggerids'] = array_unique($elements['triggers']);
121	}
122	$triggerids = API::Trigger()->get($options);
123
124	return API::Event()->get([
125		'source' => EVENT_SOURCE_TRIGGERS,
126		'object' => EVENT_OBJECT_TRIGGER,
127		'countOutput' => true,
128		'objectids' => zbx_objectValues($triggerids, 'triggerid'),
129		'filter' => [
130			'value' => $value_event,
131			'acknowledged' => $ack ? 1 : 0
132		]
133	]);
134}
135
136/**
137 *
138 * @param array  $event                   An array of event data.
139 * @param string $event['eventid']        Event ID.
140 * @param string $event['objectid']       Object ID.
141 * @param string $event['correlationid']  OK Event correlation ID.
142 * @param string $event['userid']         User ID who generated the OK event.
143 * @param string $event['name']           Event name.
144 * @param string $event['acknowledged']   State of acknowledgement.
145 * @param CCOl   $event['opdata']         Operational data with expanded macros.
146 * @param string $event['comments']       Trigger description with expanded macros.
147 *
148 * @return CTableInfo
149 */
150function make_event_details(array $event) {
151	$config = select_config();
152	$is_acknowledged = ($event['acknowledged'] == EVENT_ACKNOWLEDGED);
153
154	$table = (new CTableInfo())
155		->addRow([
156			_('Event'),
157			(new CCol($event['name']))->addClass(ZBX_STYLE_WORDWRAP)
158		])
159		->addRow([
160			_('Operational data'),
161			$event['opdata']
162		])
163		->addRow([
164			_('Severity'),
165			getSeverityCell($event['severity'], $config)
166		])
167		->addRow([
168			_('Time'),
169			zbx_date2str(DATE_TIME_FORMAT_SECONDS, $event['clock'])
170		])
171		->addRow([
172			_('Acknowledged'),
173			(new CLink($is_acknowledged ? _('Yes') : _('No')))
174				->addClass($is_acknowledged ? ZBX_STYLE_GREEN : ZBX_STYLE_RED)
175				->addClass(ZBX_STYLE_LINK_ALT)
176				->onClick('acknowledgePopUp('.json_encode(['eventids' => [$event['eventid']]]).', this);')
177		]);
178
179	if ($event['r_eventid'] != 0) {
180		if ($event['correlationid'] != 0) {
181			$correlations = API::Correlation()->get([
182				'output' => ['correlationid', 'name'],
183				'correlationids' => [$event['correlationid']]
184			]);
185
186			if ($correlations) {
187				if (CWebUser::getType() == USER_TYPE_SUPER_ADMIN) {
188					$correlation_name = (new CLink($correlations[0]['name'],
189						(new CUrl('correlation.php'))
190							->setArgument('correlationid', $correlations[0]['correlationid'])
191							->getUrl()
192					))->addClass(ZBX_STYLE_LINK_ALT);
193				}
194				else {
195					$correlation_name = $correlations[0]['name'];
196				}
197			}
198			else {
199				$correlation_name = _('Correlation rule');
200			}
201
202			$table->addRow([_('Resolved by'), $correlation_name]);
203		}
204		elseif ($event['userid'] != 0) {
205			if ($event['userid'] == CWebUser::$data['userid']) {
206				$table->addRow([_('Resolved by'), getUserFullname([
207					'alias' => CWebUser::$data['alias'],
208					'name' => CWebUser::$data['name'],
209					'surname' => CWebUser::$data['surname']
210				])]);
211			}
212			else {
213				$user = API::User()->get([
214					'output' => ['alias', 'name', 'surname'],
215					'userids' => [$event['userid']]
216				]);
217
218				if ($user) {
219					$table->addRow([_('Resolved by'), getUserFullname($user[0])]);
220				}
221				else {
222					$table->addRow([_('Resolved by'), _('Inaccessible user')]);
223				}
224			}
225		}
226		else {
227			$table->addRow([_('Resolved by'), _('Trigger')]);
228		}
229	}
230
231	$tags = makeTags([$event]);
232
233	$table
234		->addRow([_('Tags'), $tags[$event['eventid']]])
235		->addRow([_('Description'), (new CDiv(zbx_str2links($event['comments'])))]);
236
237	return $table;
238}
239
240function make_small_eventlist(array $startEvent) {
241	$config = select_config();
242
243	$table = (new CTableInfo())
244		->setHeader([
245			_('Time'),
246			_('Recovery time'),
247			_('Status'),
248			_('Age'),
249			_('Duration'),
250			_('Ack'),
251			_('Actions')
252		]);
253
254	$events = API::Event()->get([
255		'output' => ['eventid', 'source', 'object', 'objectid', 'acknowledged', 'clock', 'ns', 'severity', 'r_eventid'],
256		'select_acknowledges' => ['userid', 'clock', 'message', 'action', 'old_severity', 'new_severity'],
257		'source' => EVENT_SOURCE_TRIGGERS,
258		'object' => EVENT_OBJECT_TRIGGER,
259		'value' => TRIGGER_VALUE_TRUE,
260		'objectids' => $startEvent['objectid'],
261		'eventid_till' => $startEvent['eventid'],
262		'sortfield' => ['clock', 'eventid'],
263		'sortorder' => ZBX_SORT_DOWN,
264		'limit' => 20,
265		'preservekeys' => true
266	]);
267
268	$r_eventids = [];
269
270	foreach ($events as $event) {
271		$r_eventids[$event['r_eventid']] = true;
272	}
273	unset($r_eventids[0]);
274
275	$r_events = $r_eventids
276		? API::Event()->get([
277			'output' => ['clock'],
278			'source' => EVENT_SOURCE_TRIGGERS,
279			'object' => EVENT_OBJECT_TRIGGER,
280			'eventids' => array_keys($r_eventids),
281			'preservekeys' => true
282		])
283		: [];
284
285	$triggerids = [];
286	foreach ($events as &$event) {
287		$triggerids[] = $event['objectid'];
288
289		$event['r_clock'] = array_key_exists($event['r_eventid'], $r_events)
290			? $r_events[$event['r_eventid']]['clock']
291			: 0;
292	}
293	unset($event);
294
295	// Get trigger severities.
296	$triggers = $triggerids
297		? API::Trigger()->get([
298			'output' => ['priority'],
299			'triggerids' => $triggerids,
300			'preservekeys' => true
301		])
302		: [];
303
304	$severity_config = [
305		'severity_name_0' => $config['severity_name_0'],
306		'severity_name_1' => $config['severity_name_1'],
307		'severity_name_2' => $config['severity_name_2'],
308		'severity_name_3' => $config['severity_name_3'],
309		'severity_name_4' => $config['severity_name_4'],
310		'severity_name_5' => $config['severity_name_5']
311	];
312	$actions = getEventsActionsIconsData($events, $triggers);
313	$users = API::User()->get([
314		'output' => ['alias', 'name', 'surname'],
315		'userids' => array_keys($actions['userids']),
316		'preservekeys' => true
317	]);
318
319	foreach ($events as $event) {
320		$duration = ($event['r_eventid'] != 0)
321			? zbx_date2age($event['clock'], $event['r_clock'])
322			: zbx_date2age($event['clock']);
323
324		if ($event['r_eventid'] == 0) {
325			$in_closing = false;
326
327			foreach ($event['acknowledges'] as $acknowledge) {
328				if (($acknowledge['action'] & ZBX_PROBLEM_UPDATE_CLOSE) == ZBX_PROBLEM_UPDATE_CLOSE) {
329					$in_closing = true;
330					break;
331				}
332			}
333
334			$value = $in_closing ? TRIGGER_VALUE_FALSE : TRIGGER_VALUE_TRUE;
335			$value_str = $in_closing ? _('CLOSING') : _('PROBLEM');
336			$value_clock = $in_closing ? time() : $event['clock'];
337		}
338		else {
339			$value = TRIGGER_VALUE_FALSE;
340			$value_str = _('RESOLVED');
341			$value_clock = $event['r_clock'];
342		}
343
344		$is_acknowledged = ($event['acknowledged'] == EVENT_ACKNOWLEDGED);
345		$cell_status = new CSpan($value_str);
346
347		/*
348		 * Add colors to span depending on configuration and trigger parameters. No blinking added to status,
349		 * since the page is not on autorefresh.
350		 */
351		addTriggerValueStyle($cell_status, $value, $value_clock, $is_acknowledged);
352
353		// Create acknowledge link.
354		$problem_update_link = (new CLink($is_acknowledged ? _('Yes') : _('No')))
355			->addClass($is_acknowledged ? ZBX_STYLE_GREEN : ZBX_STYLE_RED)
356			->addClass(ZBX_STYLE_LINK_ALT)
357			->onClick('acknowledgePopUp('.json_encode(['eventids' => [$event['eventid']]]).', this);');
358
359		$table->addRow([
360			(new CLink(zbx_date2str(DATE_TIME_FORMAT_SECONDS, $event['clock']),
361				'tr_events.php?triggerid='.$event['objectid'].'&eventid='.$event['eventid']
362			))->addClass('action'),
363			($event['r_eventid'] == 0)
364				? ''
365				: (new CLink(zbx_date2str(DATE_TIME_FORMAT_SECONDS, $event['r_clock']),
366						'tr_events.php?triggerid='.$event['objectid'].'&eventid='.$event['eventid']
367				))->addClass('action'),
368			$cell_status,
369			zbx_date2age($event['clock']),
370			$duration,
371			$problem_update_link,
372			makeEventActionsIcons($event['eventid'], $actions['data'], $users, $severity_config)
373		]);
374	}
375
376	return $table;
377}
378
379/**
380 * Place filter tags at the beginning of tags array.
381 *
382 * @param array  $event_tags
383 * @param string $event_tags[]['tag']
384 * @param string $event_tags[]['value']
385 * @param array  $f_tags
386 * @param int    $f_tags[<tag>][]['operator']
387 * @param string $f_tags[<tag>][]['value']
388 *
389 * @return array
390 */
391function orderEventTags(array $event_tags, array $f_tags) {
392	$first_tags = [];
393
394	foreach ($event_tags as $i => $tag) {
395		if (array_key_exists($tag['tag'], $f_tags)) {
396			foreach ($f_tags[$tag['tag']] as $f_tag) {
397				if (($f_tag['operator'] == TAG_OPERATOR_EQUAL && $tag['value'] === $f_tag['value'])
398						|| ($f_tag['operator'] == TAG_OPERATOR_LIKE
399							&& ($f_tag['value'] === '' || stripos($tag['value'], $f_tag['value']) !== false))) {
400					$first_tags[] = $tag;
401					unset($event_tags[$i]);
402					break;
403				}
404			}
405		}
406	}
407
408	return array_merge($first_tags, $event_tags);
409}
410
411/**
412 * Place priority tags at the beginning of tags array.
413 *
414 * @param array  $event_tags             An array of event tags.
415 * @param string $event_tags[]['tag']    Tag name.
416 * @param string $event_tags[]['value']  Tag value.
417 * @param array  $priorities             An array of priority tag names.
418 *
419 * @return array
420 */
421function orderEventTagsByPriority(array $event_tags, array $priorities) {
422	$first_tags = [];
423
424	foreach ($priorities as $priority) {
425		foreach ($event_tags as $i => $tag) {
426			if ($tag['tag'] === $priority) {
427				$first_tags[] = $tag;
428				unset($event_tags[$i]);
429			}
430		}
431	}
432
433	return array_merge($first_tags, $event_tags);
434}
435
436/**
437 * Create element with tags.
438 *
439 * @param array  $list
440 * @param string $list[][$key]
441 * @param array  $list[]['tags']
442 * @param string $list[]['tags'][]['tag']
443 * @param string $list[]['tags'][]['value']
444 * @param bool   $html
445 * @param string $key                        Name of tag source ID. Possible values:
446 *                                            - 'eventid' - for events and problems (default);
447 *                                            - 'hostid' - for hosts;
448 *                                            - 'templateid' - for templates;
449 *                                            - 'triggerid' - for triggers.
450 * @param int    $list_tag_count             Maximum number of tags to display.
451 * @param array  $filter_tags                An array of tag filtering data.
452 * @param string $filter_tags[]['tag']
453 * @param int    $filter_tags[]['operator']
454 * @param string $filter_tags[]['value']
455 * @param int    $tag_name_format            Tag name format. Possible values:
456 *                                            - PROBLEMS_TAG_NAME_FULL (default);
457 *                                            - PROBLEMS_TAG_NAME_SHORTENED;
458 *                                            - PROBLEMS_TAG_NAME_NONE.
459 * @param string $tag_priority               A list of comma-separated tag names.
460 *
461 * @return array
462 */
463function makeTags(array $list, $html = true, $key = 'eventid', $list_tag_count = ZBX_TAG_COUNT_DEFAULT,
464		array $filter_tags = [], $tag_name_format = PROBLEMS_TAG_NAME_FULL, $tag_priority = '') {
465	$tags = [];
466
467	if ($html) {
468		// Convert $filter_tags to a more usable format.
469
470		$f_tags = [];
471
472		foreach ($filter_tags as $tag) {
473			$f_tags[$tag['tag']][] = [
474				'operator' => $tag['operator'],
475				'value' => $tag['value']
476			];
477		}
478	}
479
480	if ($tag_priority !== '') {
481		$p_tags = explode(',', $tag_priority);
482		$p_tags = array_map('trim', $p_tags);
483	}
484
485	foreach ($list as $element) {
486		$tags[$element[$key]] = [];
487
488		if (!$element['tags']) {
489			continue;
490		}
491
492		CArrayHelper::sort($element['tags'], ['tag', 'value']);
493
494		if ($html) {
495			// Show first n tags and "..." with hint box if there are more.
496
497			$e_tags = $f_tags ? orderEventTags($element['tags'], $f_tags) : $element['tags'];
498
499			if ($tag_priority !== '') {
500				$e_tags = orderEventTagsByPriority($e_tags, $p_tags);
501			}
502
503			$tags_shown = 0;
504
505			foreach ($e_tags as $tag) {
506				$value = getTagString($tag, $tag_name_format);
507
508				if ($value !== '') {
509					$tags[$element[$key]][] = (new CSpan($value))
510						->addClass(ZBX_STYLE_TAG)
511						->setHint(getTagString($tag));
512
513					$tags_shown++;
514
515					if ($tags_shown >= $list_tag_count) {
516						break;
517					}
518				}
519			}
520
521			if (count($element['tags']) > $tags_shown) {
522				// Display all tags in hint box.
523
524				$hint_content = [];
525
526				foreach ($element['tags'] as $tag) {
527					$value = getTagString($tag);
528					$hint_content[$element[$key]][] = (new CSpan($value))
529						->addClass(ZBX_STYLE_TAG)
530						->setHint($value);
531				}
532
533				$tags[$element[$key]][] = (new CSpan(
534					(new CButton(null))
535						->addClass(ZBX_STYLE_ICON_WZRD_ACTION)
536						->setHint(new CDiv($hint_content), '', true, 'max-width: 500px')
537				))->addClass(ZBX_STYLE_REL_CONTAINER);
538			}
539		}
540		else {
541			// Show all and uncut for CSV.
542
543			foreach ($element['tags'] as $tag) {
544				$tags[$element[$key]][] = getTagString($tag);
545			}
546		}
547	}
548
549	return $tags;
550}
551
552/**
553 * Returns tag name in selected format.
554 *
555 * @param array  $tag
556 * @param string $tag['tag']
557 * @param string $tag['value']
558 * @param int    $tag_name_format  PROBLEMS_TAG_NAME_*
559 *
560 * @return string
561 */
562function getTagString(array $tag, $tag_name_format = PROBLEMS_TAG_NAME_FULL) {
563	switch ($tag_name_format) {
564		case PROBLEMS_TAG_NAME_NONE:
565			return $tag['value'];
566
567		case PROBLEMS_TAG_NAME_SHORTENED:
568			return substr($tag['tag'], 0, 3).(($tag['value'] === '') ? '' : ': '.$tag['value']);
569
570		default:
571			return $tag['tag'].(($tag['value'] === '') ? '' : ': '.$tag['value']);
572	}
573}
574