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_AUTO_REGISTRATION => _('auto registration'),
37		EVENT_SOURCE_INTERNAL => _x('internal', 'event source')
38	];
39
40	if ($source === null) {
41		return $sources;
42	}
43	elseif (isset($sources[$source])) {
44		return $sources[$source];
45	}
46	else {
47		return _('Unknown');
48	}
49}
50
51/**
52 * Returns the names of supported event objects.
53 *
54 * If the $source parameter is passed, returns the name of the specific object, otherwise - returns an array of all
55 * supported objects.
56 *
57 * @param int $object
58 *
59 * @return array|string
60 */
61function eventObject($object = null) {
62	$objects = [
63		EVENT_OBJECT_TRIGGER => _('trigger'),
64		EVENT_OBJECT_DHOST => _('discovered host'),
65		EVENT_OBJECT_DSERVICE => _('discovered service'),
66		EVENT_OBJECT_AUTOREGHOST => _('auto-registered host'),
67		EVENT_OBJECT_ITEM => _('item'),
68		EVENT_OBJECT_LLDRULE => _('low-level discovery rule')
69	];
70
71	if ($object === null) {
72		return $objects;
73	}
74	elseif (isset($objects[$object])) {
75		return $objects[$object];
76	}
77	else {
78		return _('Unknown');
79	}
80}
81
82/**
83 * Returns all supported event source-object pairs.
84 *
85 * @return array
86 */
87function eventSourceObjects() {
88	return [
89		['source' => EVENT_SOURCE_TRIGGERS, 'object' => EVENT_OBJECT_TRIGGER],
90		['source' => EVENT_SOURCE_DISCOVERY, 'object' => EVENT_OBJECT_DHOST],
91		['source' => EVENT_SOURCE_DISCOVERY, 'object' => EVENT_OBJECT_DSERVICE],
92		['source' => EVENT_SOURCE_AUTO_REGISTRATION, 'object' => EVENT_OBJECT_AUTOREGHOST],
93		['source' => EVENT_SOURCE_INTERNAL, 'object' => EVENT_OBJECT_TRIGGER],
94		['source' => EVENT_SOURCE_INTERNAL, 'object' => EVENT_OBJECT_ITEM],
95		['source' => EVENT_SOURCE_INTERNAL, 'object' => EVENT_OBJECT_LLDRULE]
96	];
97}
98
99function get_events_unacknowledged($db_element, $value_trigger = null, $value_event = null, $ack = false) {
100	$elements = ['hosts' => [], 'hosts_groups' => [], 'triggers' => []];
101	get_map_elements($db_element, $elements);
102
103	if (empty($elements['hosts_groups']) && empty($elements['hosts']) && empty($elements['triggers'])) {
104		return 0;
105	}
106
107	$config = select_config();
108	$options = [
109		'output' => ['triggerid'],
110		'monitored' => 1,
111		'skipDependent' => 1,
112		'limit' => $config['search_limit'] + 1
113	];
114	if (!is_null($value_trigger)) {
115		$options['filter'] = ['value' => $value_trigger];
116	}
117	if (!empty($elements['hosts_groups'])) {
118		$options['groupids'] = array_unique($elements['hosts_groups']);
119	}
120	if (!empty($elements['hosts'])) {
121		$options['hostids'] = array_unique($elements['hosts']);
122	}
123	if (!empty($elements['triggers'])) {
124		$options['triggerids'] = array_unique($elements['triggers']);
125	}
126	$triggerids = API::Trigger()->get($options);
127
128	return API::Event()->get([
129		'source' => EVENT_SOURCE_TRIGGERS,
130		'object' => EVENT_OBJECT_TRIGGER,
131		'countOutput' => true,
132		'objectids' => zbx_objectValues($triggerids, 'triggerid'),
133		'filter' => [
134			'value' => $value_event,
135			'acknowledged' => $ack ? 1 : 0
136		]
137	]);
138}
139
140function get_next_event($currentEvent, array $eventList = []) {
141	$nextEvent = false;
142
143	foreach ($eventList as $event) {
144		// check only the events belonging to the same object
145		// find the event with the smallest eventid but greater than the current event id
146		if ($event['object'] == $currentEvent['object'] && bccomp($event['objectid'], $currentEvent['objectid']) == 0
147				&& (bccomp($event['eventid'], $currentEvent['eventid']) === 1
148				&& (!$nextEvent || bccomp($event['eventid'], $nextEvent['eventid']) === -1))) {
149			$nextEvent = $event;
150		}
151	}
152	if ($nextEvent) {
153		return $nextEvent;
154	}
155
156	$sql = 'SELECT e.*'.
157			' FROM events e'.
158			' WHERE e.source='.zbx_dbstr($currentEvent['source']).
159				' AND e.object='.zbx_dbstr($currentEvent['object']).
160				' AND e.objectid='.zbx_dbstr($currentEvent['objectid']).
161				' AND e.clock>='.zbx_dbstr($currentEvent['clock']).
162				' AND ((e.clock='.zbx_dbstr($currentEvent['clock']).' AND e.ns>'.$currentEvent['ns'].')'.
163					' OR e.clock>'.zbx_dbstr($currentEvent['clock']).')'.
164			' ORDER BY e.clock,e.eventid';
165	return DBfetch(DBselect($sql, 1));
166}
167
168function make_event_details($event, $trigger, $backurl) {
169	$config = select_config();
170	$table = (new CTableInfo())
171		->addRow([
172			_('Event'),
173			CMacrosResolverHelper::resolveEventDescription(array_merge($trigger, $event))
174		])
175		->addRow([
176			_('Time'),
177			zbx_date2str(DATE_TIME_FORMAT_SECONDS, $event['clock'])
178		]);
179
180	if ($config['event_ack_enable']) {
181		// to make resulting link not have hint with acknowledges
182		$event['acknowledges'] = count($event['acknowledges']);
183		$table->addRow([_('Acknowledged'), getEventAckState($event, $backurl)]);
184	}
185
186	return $table;
187}
188
189function make_small_eventlist($startEvent, $backurl) {
190	$config = select_config();
191
192	$table = (new CTableInfo())
193		->setHeader([
194			_('Time'),
195			_('Status'),
196			_('Duration'),
197			_('Age'),
198			$config['event_ack_enable'] ? _('Ack') : null, // if we need to chow acks
199			_('Actions')
200		]);
201
202	$clock = $startEvent['clock'];
203
204	$events = API::Event()->get([
205		'source' => EVENT_SOURCE_TRIGGERS,
206		'object' => EVENT_OBJECT_TRIGGER,
207		'objectids' => $startEvent['objectid'],
208		'eventid_till' => $startEvent['eventid'],
209		'output' => API_OUTPUT_EXTEND,
210		'select_acknowledges' => API_OUTPUT_COUNT,
211		'sortfield' => ['clock', 'eventid'],
212		'sortorder' => ZBX_SORT_DOWN,
213		'limit' => 20
214	]);
215
216	$sortFields = [
217		['field' => 'clock', 'order' => ZBX_SORT_DOWN],
218		['field' => 'eventid', 'order' => ZBX_SORT_DOWN]
219	];
220	CArrayHelper::sort($events, $sortFields);
221
222	$actions = makeEventsActions(zbx_objectValues($events, 'eventid'));
223
224	foreach ($events as $event) {
225		$lclock = $clock;
226		$duration = zbx_date2age($lclock, $event['clock']);
227		$clock = $event['clock'];
228
229		if (bccomp($startEvent['eventid'],$event['eventid']) == 0 && $nextevent = get_next_event($event, $events)) {
230			$duration = zbx_date2age($nextevent['clock'], $clock);
231		}
232		elseif (bccomp($startEvent['eventid'], $event['eventid']) == 0) {
233			$duration = zbx_date2age($clock);
234		}
235
236		$eventStatusSpan = new CSpan(trigger_value2str($event['value']));
237
238		// add colors and blinking to span depending on configuration and trigger parameters
239		addTriggerValueStyle(
240			$eventStatusSpan,
241			$event['value'],
242			$event['clock'],
243			$event['acknowledged']
244		);
245
246		$table->addRow([
247			(new CLink(
248				zbx_date2str(DATE_TIME_FORMAT_SECONDS, $event['clock']),
249				'tr_events.php?triggerid='.$event['objectid'].'&eventid='.$event['eventid']))
250				->addClass('action'),
251			$eventStatusSpan,
252			$duration,
253			zbx_date2age($event['clock']),
254			$config['event_ack_enable'] ? getEventAckState($event, $backurl) : null,
255			(new CCol(isset($actions[$event['eventid']]) ? $actions[$event['eventid']] : ''))
256				->addClass(ZBX_STYLE_NOWRAP)
257		]);
258	}
259
260	return $table;
261}
262
263/**
264 * Create table with trigger description and events.
265 *
266 * @param array  $trigger							An array of trigger data.
267 * @param string $trigger['triggerid']				Trigger ID to select events.
268 * @param string $trigger['description']			Trigger description.
269 * @param string $trigger['url']					Trigger URL.
270 * @param string $trigger['lastEvent']['eventid']	Last event ID
271 * @param string $backurl							URL to return to.
272 *
273 * @return CDiv
274 */
275function make_popup_eventlist($trigger, $backurl) {
276	// Show trigger description and URL.
277	$div = (new CDiv());
278
279	if ($trigger['comments'] !== '') {
280		$div->addItem(
281			(new CDiv())
282				->addItem(zbx_str2links($trigger['comments']))
283				->addClass(ZBX_STYLE_OVERLAY_DESCR)
284		);
285	}
286
287	if ($trigger['url'] !== '') {
288		$trigger_url = CHtmlUrlValidator::validate($trigger['url'], false)
289			? $trigger['url']
290			: 'javascript: alert(\''._s('Provided URL "%1$s" is invalid.', zbx_jsvalue($trigger['url'], false, false)).
291				'\');';
292
293		$div->addItem(
294			(new CDiv())
295				->addItem(new CLink(CHTML::encode($trigger['url']), $trigger_url))
296				->addClass(ZBX_STYLE_OVERLAY_DESCR_URL)
297		);
298	}
299
300	// Select and show events.
301	$config = select_config();
302
303	$table = new CTableInfo();
304
305	// If acknowledges are turned on, we show 'ack' column.
306	if ($config['event_ack_enable']) {
307		$table->setHeader([_('Time'), _('Status'), _('Duration'), _('Age'), _('Ack')]);
308	}
309	else {
310		$table->setHeader([_('Time'), _('Status'), _('Duration'), _('Age')]);
311	}
312
313	if ($trigger['lastEvent']) {
314		$events = API::Event()->get([
315			'source' => EVENT_SOURCE_TRIGGERS,
316			'object' => EVENT_OBJECT_TRIGGER,
317			'output' => API_OUTPUT_EXTEND,
318			'objectids' => [$trigger['triggerid']],
319			'eventid_till' => $trigger['lastEvent']['eventid'],
320			'select_acknowledges' => API_OUTPUT_COUNT,
321			'sortfield' => ['clock', 'eventid'],
322			'sortorder' => ZBX_SORT_DOWN,
323			'limit' => ZBX_WIDGET_ROWS
324		]);
325
326		$lclock = time();
327
328		foreach ($events as $event) {
329			$duration = zbx_date2age($lclock, $event['clock']);
330			$lclock = $event['clock'];
331
332			$eventStatusSpan = new CSpan(trigger_value2str($event['value']));
333
334			// add colors and blinking to span depending on configuration and trigger parameters
335			addTriggerValueStyle($eventStatusSpan, $event['value'], $event['clock'], $event['acknowledged']);
336
337			$table->addRow([
338				zbx_date2str(DATE_TIME_FORMAT_SECONDS, $event['clock']),
339				$eventStatusSpan,
340				$duration,
341				zbx_date2age($event['clock']),
342				$config['event_ack_enable'] ? getEventAckState($event, $backurl) : null
343			]);
344		}
345	}
346
347	$div->addItem($table);
348
349	return $div;
350}
351
352/**
353 * Create element with event acknowledges info.
354 * If $event has subarray 'acknowledges', returned link will have hint with acknowledges.
355 *
356 * @param array			$event   event data
357 * @param int			$event['acknowledged']
358 * @param int			$event['eventid']
359 * @param int			$event['objectid']
360 * @param mixed			$event['acknowledges']
361 * @param string		$backurl  add url param to link with current page file name
362 *
363 * @return CLink
364 */
365function getEventAckState($event, $backurl) {
366	if ($event['acknowledged'] == EVENT_ACKNOWLEDGED) {
367		$acknowledges_num = is_array($event['acknowledges']) ? count($event['acknowledges']) : $event['acknowledges'];
368	}
369
370	$link = 'zabbix.php?action=acknowledge.edit&eventids[]='.$event['eventid'].'&backurl='.urlencode($backurl);
371
372	if ($event['acknowledged'] == EVENT_ACKNOWLEDGED) {
373		$ack = (new CLink(_('Yes'), $link))
374			->addClass(ZBX_STYLE_LINK_ALT)
375			->addClass(ZBX_STYLE_GREEN);
376		if (is_array($event['acknowledges'])) {
377			$ack->setHint(makeAckTab(array_slice($event['acknowledges'], 0, ZBX_WIDGET_ROWS)), '', false);
378		}
379		$ack = [$ack, CViewHelper::showNum($acknowledges_num)];
380	}
381	else {
382		$ack = (new CLink(_('No'), $link))
383			->addClass(ZBX_STYLE_LINK_ALT)
384			->addClass(ZBX_STYLE_RED);
385	}
386
387	return $ack;
388}
389
390function getLastEvents($options) {
391	if (!isset($options['limit'])) {
392		$options['limit'] = 15;
393	}
394
395	$triggerOptions = [
396		'filter' => [],
397		'skipDependent' => 1,
398		'selectHosts' => ['hostid', 'name'],
399		'output' => API_OUTPUT_EXTEND,
400		'sortfield' => 'lastchange',
401		'sortorder' => ZBX_SORT_DOWN,
402		'limit' => $options['triggerLimit']
403	];
404
405	$eventOptions = [
406		'source' => EVENT_SOURCE_TRIGGERS,
407		'object' => EVENT_OBJECT_TRIGGER,
408		'output' => API_OUTPUT_EXTEND,
409		'sortfield' => ['clock', 'eventid'],
410		'sortorder' => ZBX_SORT_DOWN
411	];
412
413	if (isset($options['eventLimit'])) {
414		$eventOptions['limit'] = $options['eventLimit'];
415	}
416
417	if (isset($options['priority'])) {
418		$triggerOptions['filter']['priority'] = $options['priority'];
419	}
420	if (isset($options['monitored'])) {
421		$triggerOptions['monitored'] = $options['monitored'];
422	}
423	if (isset($options['lastChangeSince'])) {
424		$triggerOptions['lastChangeSince'] = $options['lastChangeSince'];
425		$eventOptions['time_from'] = $options['lastChangeSince'];
426	}
427	if (isset($options['value'])) {
428		$triggerOptions['filter']['value'] = $options['value'];
429		$eventOptions['value'] = $options['value'];
430	}
431
432	// triggers
433	$triggers = API::Trigger()->get($triggerOptions);
434	$triggers = zbx_toHash($triggers, 'triggerid');
435
436	// events
437	$eventOptions['objectids'] = zbx_objectValues($triggers, 'triggerid');
438	$events = API::Event()->get($eventOptions);
439
440	$sortClock = [];
441	$sortEvent = [];
442	foreach ($events as $enum => $event) {
443		if (!isset($triggers[$event['objectid']])) {
444			continue;
445		}
446
447		$events[$enum]['trigger'] = $triggers[$event['objectid']];
448		$events[$enum]['host'] = reset($events[$enum]['trigger']['hosts']);
449		$sortClock[$enum] = $event['clock'];
450		$sortEvent[$enum] = $event['eventid'];
451
452		//expanding description for the state where event was
453		$merged_event = array_merge($event, $triggers[$event['objectid']]);
454		$events[$enum]['trigger']['description'] = CMacrosResolverHelper::resolveEventDescription($merged_event);
455	}
456	array_multisort($sortClock, SORT_DESC, $sortEvent, SORT_DESC, $events);
457
458	return $events;
459}
460