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
22function condition_operator2str($operator) {
23	$operators = [
24		CONDITION_OPERATOR_EQUAL  => _('equals'),
25		CONDITION_OPERATOR_NOT_EQUAL  => _('does not equal'),
26		CONDITION_OPERATOR_LIKE  => _('contains'),
27		CONDITION_OPERATOR_NOT_LIKE  => _('does not contain'),
28		CONDITION_OPERATOR_IN => _('in'),
29		CONDITION_OPERATOR_MORE_EQUAL => _('is greater than or equals'),
30		CONDITION_OPERATOR_LESS_EQUAL => _('is less than or equals'),
31		CONDITION_OPERATOR_NOT_IN => _('not in'),
32		CONDITION_OPERATOR_YES => _('Yes'),
33		CONDITION_OPERATOR_NO => _('No'),
34		CONDITION_OPERATOR_REGEXP => _('matches'),
35		CONDITION_OPERATOR_NOT_REGEXP => _('does not match')
36	];
37
38	return $operators[$operator];
39}
40
41function condition_type2str($type) {
42	$types = [
43		CONDITION_TYPE_SUPPRESSED => _('Problem is suppressed'),
44		CONDITION_TYPE_TRIGGER_NAME => _('Trigger name'),
45		CONDITION_TYPE_TRIGGER_SEVERITY => _('Trigger severity'),
46		CONDITION_TYPE_TRIGGER => _('Trigger'),
47		CONDITION_TYPE_HOST_NAME => _('Host name'),
48		CONDITION_TYPE_HOST_GROUP => _('Host group'),
49		CONDITION_TYPE_TEMPLATE => _('Template'),
50		CONDITION_TYPE_HOST => _('Host'),
51		CONDITION_TYPE_TIME_PERIOD => _('Time period'),
52		CONDITION_TYPE_DRULE => _('Discovery rule'),
53		CONDITION_TYPE_DCHECK => _('Discovery check'),
54		CONDITION_TYPE_DOBJECT => _('Discovery object'),
55		CONDITION_TYPE_DHOST_IP => _('Host IP'),
56		CONDITION_TYPE_DSERVICE_TYPE => _('Service type'),
57		CONDITION_TYPE_DSERVICE_PORT => _('Service port'),
58		CONDITION_TYPE_DSTATUS => _('Discovery status'),
59		CONDITION_TYPE_DUPTIME => _('Uptime/Downtime'),
60		CONDITION_TYPE_DVALUE => _('Received value'),
61		CONDITION_TYPE_EVENT_ACKNOWLEDGED => _('Event acknowledged'),
62		CONDITION_TYPE_PROXY => _('Proxy'),
63		CONDITION_TYPE_EVENT_TYPE => _('Event type'),
64		CONDITION_TYPE_HOST_METADATA => _('Host metadata'),
65		CONDITION_TYPE_EVENT_TAG => _('Tag name'),
66		CONDITION_TYPE_EVENT_TAG_VALUE => _('Tag value')
67	];
68
69	return $types[$type];
70}
71
72function discovery_object2str($object = null) {
73	$objects = [
74		EVENT_OBJECT_DHOST => _('Device'),
75		EVENT_OBJECT_DSERVICE => _('Service')
76	];
77
78	if ($object === null) {
79		return $objects;
80	}
81
82	return $objects[$object];
83}
84
85/**
86 * Converts numerical action condition values to their corresponding string values according to action condition type.
87 *
88 * For action condition types such as: hosts, host groups, templates, proxies, triggers, discovery rules
89 * and discovery checks, action condition values contain IDs. All unique IDs are first collected and then queried.
90 * For other action condition types values are returned as they are or converted using simple string conversion
91 * functions according to action condition type.
92 *
93 * @param array $actions							array of actions
94 * @param array $action['filter']					array containing arrays of action conditions and other data
95 * @param array $action['filter']['conditions']		array of action conditions
96 *
97 * @return array									returns an array of actions condition string values
98 */
99function actionConditionValueToString(array $actions) {
100	$result = [];
101
102	$groupIds = [];
103	$triggerIds = [];
104	$hostIds = [];
105	$templateIds = [];
106	$proxyIds = [];
107	$dRuleIds = [];
108	$dCheckIds = [];
109
110	foreach ($actions as $i => $action) {
111		$result[$i] = [];
112
113		foreach ($action['filter']['conditions'] as $j => $condition) {
114			// unknown types and all of the default values for other types are 'Unknown'
115			$result[$i][$j] = _('Unknown');
116
117			switch ($condition['conditiontype']) {
118				case CONDITION_TYPE_HOST_GROUP:
119					$groupIds[$condition['value']] = $condition['value'];
120					break;
121
122				case CONDITION_TYPE_TRIGGER:
123					$triggerIds[$condition['value']] = $condition['value'];
124					break;
125
126				case CONDITION_TYPE_HOST:
127					$hostIds[$condition['value']] = $condition['value'];
128					break;
129
130				case CONDITION_TYPE_TEMPLATE:
131					$templateIds[$condition['value']] = $condition['value'];
132					break;
133
134				case CONDITION_TYPE_PROXY:
135					$proxyIds[$condition['value']] = $condition['value'];
136					break;
137
138				// return values as is for following condition types
139				case CONDITION_TYPE_TRIGGER_NAME:
140				case CONDITION_TYPE_HOST_METADATA:
141				case CONDITION_TYPE_HOST_NAME:
142				case CONDITION_TYPE_TIME_PERIOD:
143				case CONDITION_TYPE_DHOST_IP:
144				case CONDITION_TYPE_DSERVICE_PORT:
145				case CONDITION_TYPE_DUPTIME:
146				case CONDITION_TYPE_DVALUE:
147				case CONDITION_TYPE_EVENT_TAG:
148				case CONDITION_TYPE_EVENT_TAG_VALUE:
149					$result[$i][$j] = $condition['value'];
150					break;
151
152				case CONDITION_TYPE_EVENT_ACKNOWLEDGED:
153					$result[$i][$j] = $condition['value'] ? _('Ack') : _('Not Ack');
154					break;
155
156				case CONDITION_TYPE_TRIGGER_SEVERITY:
157					$result[$i][$j] = getSeverityName($condition['value']);
158					break;
159
160				case CONDITION_TYPE_DRULE:
161					$dRuleIds[$condition['value']] = $condition['value'];
162					break;
163
164				case CONDITION_TYPE_DCHECK:
165					$dCheckIds[$condition['value']] = $condition['value'];
166					break;
167
168				case CONDITION_TYPE_DOBJECT:
169					$result[$i][$j] = discovery_object2str($condition['value']);
170					break;
171
172				case CONDITION_TYPE_DSERVICE_TYPE:
173					$result[$i][$j] = discovery_check_type2str($condition['value']);
174					break;
175
176				case CONDITION_TYPE_DSTATUS:
177					$result[$i][$j] = discovery_object_status2str($condition['value']);
178					break;
179
180				case CONDITION_TYPE_EVENT_TYPE:
181					$result[$i][$j] = eventType($condition['value']);
182					break;
183			}
184		}
185	}
186
187	$groups = [];
188	$triggers = [];
189	$hosts = [];
190	$templates = [];
191	$proxies = [];
192	$dRules = [];
193	$dChecks = [];
194
195	if ($groupIds) {
196		$groups = API::HostGroup()->get([
197			'output' => ['name'],
198			'groupids' => $groupIds,
199			'preservekeys' => true
200		]);
201	}
202
203	if ($triggerIds) {
204		$triggers = API::Trigger()->get([
205			'output' => ['description'],
206			'triggerids' => $triggerIds,
207			'expandDescription' => true,
208			'selectHosts' => ['name'],
209			'preservekeys' => true
210		]);
211	}
212
213	if ($hostIds) {
214		$hosts = API::Host()->get([
215			'output' => ['name'],
216			'hostids' => $hostIds,
217			'preservekeys' => true
218		]);
219	}
220
221	if ($templateIds) {
222		$templates = API::Template()->get([
223			'output' => ['name'],
224			'templateids' => $templateIds,
225			'preservekeys' => true
226		]);
227	}
228
229	if ($proxyIds) {
230		$proxies = API::Proxy()->get([
231			'output' => ['host'],
232			'proxyids' => $proxyIds,
233			'preservekeys' => true
234		]);
235	}
236
237	if ($dRuleIds) {
238		$dRules = API::DRule()->get([
239			'output' => ['name'],
240			'druleids' => $dRuleIds,
241			'preservekeys' => true
242		]);
243	}
244
245	if ($dCheckIds) {
246		$dChecks = API::DCheck()->get([
247			'output' => ['type', 'key_', 'ports'],
248			'dcheckids' => $dCheckIds,
249			'selectDRules' => ['name'],
250			'preservekeys' => true
251		]);
252	}
253
254	if ($groups || $triggers || $hosts || $templates || $proxies || $dRules || $dChecks) {
255		foreach ($actions as $i => $action) {
256			foreach ($action['filter']['conditions'] as $j => $condition) {
257				$id = $condition['value'];
258
259				switch ($condition['conditiontype']) {
260					case CONDITION_TYPE_HOST_GROUP:
261						if (isset($groups[$id])) {
262							$result[$i][$j] = $groups[$id]['name'];
263						}
264						break;
265
266					case CONDITION_TYPE_TRIGGER:
267						if (isset($triggers[$id])) {
268							$host = reset($triggers[$id]['hosts']);
269							$result[$i][$j] = $host['name'].NAME_DELIMITER.$triggers[$id]['description'];
270						}
271						break;
272
273					case CONDITION_TYPE_HOST:
274						if (isset($hosts[$id])) {
275							$result[$i][$j] = $hosts[$id]['name'];
276						}
277						break;
278
279					case CONDITION_TYPE_TEMPLATE:
280						if (isset($templates[$id])) {
281							$result[$i][$j] = $templates[$id]['name'];
282						}
283						break;
284
285					case CONDITION_TYPE_PROXY:
286						if (isset($proxies[$id])) {
287							$result[$i][$j] = $proxies[$id]['host'];
288						}
289						break;
290
291					case CONDITION_TYPE_DRULE:
292						if (isset($dRules[$id])) {
293							$result[$i][$j] = $dRules[$id]['name'];
294						}
295						break;
296
297					case CONDITION_TYPE_DCHECK:
298						if (isset($dChecks[$id])) {
299							$drule = reset($dChecks[$id]['drules']);
300							$type = $dChecks[$id]['type'];
301							$key_ = $dChecks[$id]['key_'];
302							$ports = $dChecks[$id]['ports'];
303
304							$dCheck = discovery_check2str($type, $key_, $ports);
305
306							$result[$i][$j] = $drule['name'].NAME_DELIMITER.$dCheck;
307						}
308						break;
309				}
310			}
311		}
312	}
313
314	return $result;
315}
316
317/**
318 * Returns the HTML representation of an action condition and action operation condition.
319 *
320 * @param string $condition_type
321 * @param string $operator
322 * @param string $value
323 * @param string $value2
324 *
325 * @return array
326 */
327function getConditionDescription($condition_type, $operator, $value, $value2) {
328	if ($condition_type == CONDITION_TYPE_EVENT_TAG_VALUE) {
329		$description = [_('Value of tag')];
330		$description[] = ' ';
331		$description[] = italic(CHtml::encode($value2));
332		$description[] = ' ';
333	}
334	elseif ($condition_type == CONDITION_TYPE_SUPPRESSED) {
335		return ($operator == CONDITION_OPERATOR_YES)
336			? [_('Problem is suppressed')]
337			: [_('Problem is not suppressed')];
338	}
339	elseif ($condition_type == CONDITION_TYPE_EVENT_ACKNOWLEDGED) {
340		return $value ? _('Event is acknowledged') : _('Event is not acknowledged');
341	}
342	else {
343		$description = [condition_type2str($condition_type)];
344		$description[] = ' ';
345	}
346
347	$description[] = condition_operator2str($operator);
348	$description[] = ' ';
349	$description[] = italic(CHtml::encode($value));
350
351	return $description;
352}
353
354/**
355 * Gathers media types, user groups, users, host groups, hosts and templates for actions and their operations, and
356 * returns the HTML representation of action operation values according to action operation type.
357 *
358 * @param array $actions				Array of actions
359 * @param int $type						Operations recovery type (ACTION_OPERATION or ACTION_RECOVERY_OPERATION)
360 *
361 * @return array						Returns an array of actions operation descriptions.
362 */
363function getActionOperationDescriptions(array $actions, $type) {
364	$result = [];
365
366	$media_typeids = [];
367	$userids = [];
368	$usr_grpids = [];
369	$hostids = [];
370	$groupids = [];
371	$templateids = [];
372	$scriptids = [];
373
374	foreach ($actions as $i => $action) {
375		$result[$i] = [];
376
377		if ($type == ACTION_OPERATION) {
378			foreach ($action['operations'] as $j => $operation) {
379				$result[$i][$j] = [];
380
381				switch ($operation['operationtype']) {
382					case OPERATION_TYPE_MESSAGE:
383						$media_typeid = $operation['opmessage']['mediatypeid'];
384
385						if ($media_typeid != 0) {
386							$media_typeids[$media_typeid] = $media_typeid;
387						}
388
389						if (array_key_exists('opmessage_usr', $operation) && $operation['opmessage_usr']) {
390							foreach ($operation['opmessage_usr'] as $users) {
391								$userids[$users['userid']] = $users['userid'];
392							}
393						}
394
395						if (array_key_exists('opmessage_grp', $operation) && $operation['opmessage_grp']) {
396							foreach ($operation['opmessage_grp'] as $user_groups) {
397								$usr_grpids[$user_groups['usrgrpid']] = $user_groups['usrgrpid'];
398							}
399						}
400						break;
401
402					case OPERATION_TYPE_COMMAND:
403						if (array_key_exists('opcommand_hst', $operation) && $operation['opcommand_hst']) {
404							foreach ($operation['opcommand_hst'] as $host) {
405								if ($host['hostid'] != 0) {
406									$hostids[$host['hostid']] = $host['hostid'];
407								}
408							}
409						}
410
411						if (array_key_exists('opcommand_grp', $operation) && $operation['opcommand_grp']) {
412							foreach ($operation['opcommand_grp'] as $host_group) {
413								$groupids[$host_group['groupid']] = true;
414							}
415						}
416
417						$scriptids[$operation['opcommand']['scriptid']] = true;
418						break;
419
420					case OPERATION_TYPE_GROUP_ADD:
421					case OPERATION_TYPE_GROUP_REMOVE:
422						foreach ($operation['opgroup'] as $groupid) {
423							$groupids[$groupid['groupid']] = true;
424						}
425						break;
426
427					case OPERATION_TYPE_TEMPLATE_ADD:
428					case OPERATION_TYPE_TEMPLATE_REMOVE:
429						foreach ($operation['optemplate'] as $templateid) {
430							$templateids[$templateid['templateid']] = true;
431						}
432						break;
433				}
434			}
435		}
436		else {
437			$operations_key = ($type == ACTION_RECOVERY_OPERATION)
438				? 'recovery_operations'
439				: 'ack_operations';
440
441			foreach ($action[$operations_key] as $j => $operation) {
442				$result[$i][$j] = [];
443
444				switch ($operation['operationtype']) {
445					case OPERATION_TYPE_MESSAGE:
446						$media_typeid = $operation['opmessage']['mediatypeid'];
447
448						if ($media_typeid != 0) {
449							$media_typeids[$media_typeid] = $media_typeid;
450						}
451
452						if (array_key_exists('opmessage_usr', $operation) && $operation['opmessage_usr']) {
453							foreach ($operation['opmessage_usr'] as $users) {
454								$userids[$users['userid']] = $users['userid'];
455							}
456						}
457
458						if (array_key_exists('opmessage_grp', $operation) && $operation['opmessage_grp']) {
459							foreach ($operation['opmessage_grp'] as $user_groups) {
460								$usr_grpids[$user_groups['usrgrpid']] = $user_groups['usrgrpid'];
461							}
462						}
463						break;
464
465					case OPERATION_TYPE_COMMAND:
466						if (array_key_exists('opcommand_hst', $operation) && $operation['opcommand_hst']) {
467							foreach ($operation['opcommand_hst'] as $host) {
468								if ($host['hostid'] != 0) {
469									$hostids[$host['hostid']] = $host['hostid'];
470								}
471							}
472						}
473
474						if (array_key_exists('opcommand_grp', $operation) && $operation['opcommand_grp']) {
475							foreach ($operation['opcommand_grp'] as $host_group) {
476								$groupids[$host_group['groupid']] = true;
477							}
478						}
479
480						$scriptids[$operation['opcommand']['scriptid']] = true;
481						break;
482				}
483			}
484		}
485	}
486
487	$media_types = [];
488	$users = [];
489	$user_groups = [];
490	$hosts = [];
491	$host_groups = [];
492	$templates = [];
493	$scripts = [];
494
495	if ($media_typeids) {
496		$media_types = API::Mediatype()->get([
497			'output' => ['name'],
498			'mediatypeids' => $media_typeids,
499			'preservekeys' => true
500		]);
501	}
502
503	if ($userids) {
504		$fullnames = [];
505
506		$users = API::User()->get([
507			'output' => ['userid', 'username', 'name', 'surname'],
508			'userids' => $userids
509		]);
510
511		foreach ($users as $user) {
512			$fullnames[$user['userid']] = getUserFullname($user);
513		}
514	}
515
516	if ($usr_grpids) {
517		$user_groups = API::UserGroup()->get([
518			'output' => ['name'],
519			'usrgrpids' => $usr_grpids,
520			'preservekeys' => true
521		]);
522	}
523
524	if ($hostids) {
525		$hosts = API::Host()->get([
526			'output' => ['name'],
527			'hostids' => $hostids,
528			'preservekeys' => true
529		]);
530	}
531
532	if ($groupids) {
533		$host_groups = API::HostGroup()->get([
534			'output' => ['name'],
535			'groupids' => array_keys($groupids),
536			'preservekeys' => true
537		]);
538	}
539
540	if ($templateids) {
541		$templates = API::Template()->get([
542			'output' => ['name'],
543			'templateids' => array_keys($templateids),
544			'preservekeys' => true
545		]);
546	}
547
548	if ($scriptids) {
549		$scripts = API::Script()->get([
550			'output' => ['name'],
551			'scriptids' => array_keys($scriptids),
552			'filter' => ['scope' => ZBX_SCRIPT_SCOPE_ACTION],
553			'preservekeys' => true
554		]);
555	}
556
557	// Format the HTML output.
558	foreach ($actions as $i => $action) {
559		if ($type == ACTION_OPERATION) {
560			foreach ($action['operations'] as $j => $operation) {
561				switch ($operation['operationtype']) {
562					case OPERATION_TYPE_MESSAGE:
563						$media_type = _('all media');
564						$media_typeid = $operation['opmessage']['mediatypeid'];
565
566						if ($media_typeid != 0 && isset($media_types[$media_typeid])) {
567							$media_type = $media_types[$media_typeid]['name'];
568						}
569
570						if (array_key_exists('opmessage_usr', $operation) && $operation['opmessage_usr']) {
571							$user_names_list = [];
572
573							foreach ($operation['opmessage_usr'] as $user) {
574								if (isset($fullnames[$user['userid']])) {
575									$user_names_list[] = $fullnames[$user['userid']];
576								}
577							}
578
579							order_result($user_names_list);
580
581							$result[$i][$j][] = bold(_('Send message to users').': ');
582							$result[$i][$j][] = [implode(', ', $user_names_list), SPACE, _('via'), SPACE,
583								$media_type
584							];
585							$result[$i][$j][] = BR();
586						}
587
588						if (array_key_exists('opmessage_grp', $operation) && $operation['opmessage_grp']) {
589							$user_groups_list = [];
590
591							foreach ($operation['opmessage_grp'] as $userGroup) {
592								if (isset($user_groups[$userGroup['usrgrpid']])) {
593									$user_groups_list[] = $user_groups[$userGroup['usrgrpid']]['name'];
594								}
595							}
596
597							order_result($user_groups_list);
598
599							$result[$i][$j][] = bold(_('Send message to user groups').': ');
600							$result[$i][$j][] = [implode(', ', $user_groups_list), SPACE, _('via'), SPACE,
601								$media_type
602							];
603							$result[$i][$j][] = BR();
604						}
605						break;
606
607					case OPERATION_TYPE_COMMAND:
608						$scriptid = $operation['opcommand']['scriptid'];
609
610						if (array_key_exists('opcommand_hst', $operation) && $operation['opcommand_hst']) {
611							$host_list = [];
612
613							foreach ($operation['opcommand_hst'] as $host) {
614								if ($host['hostid'] == 0) {
615									$result[$i][$j][] = [
616										bold(_s('Run script "%1$s" on current host', $scripts[$scriptid]['name'])),
617										BR()
618									];
619								}
620								elseif (isset($hosts[$host['hostid']])) {
621									$host_list[] = $hosts[$host['hostid']]['name'];
622								}
623							}
624
625							if ($host_list) {
626								order_result($host_list);
627
628								$result[$i][$j][] = bold(
629									_s('Run script "%1$s" on hosts', $scripts[$scriptid]['name']).': '
630								);
631								$result[$i][$j][] = [implode(', ', $host_list), BR()];
632							}
633						}
634
635						if (array_key_exists('opcommand_grp', $operation) && $operation['opcommand_grp']) {
636							$host_group_list = [];
637
638							foreach ($operation['opcommand_grp'] as $host_group) {
639								if (isset($host_groups[$host_group['groupid']])) {
640									$host_group_list[] = $host_groups[$host_group['groupid']]['name'];
641								}
642							}
643
644							order_result($host_group_list);
645
646							$result[$i][$j][] = bold(
647								_s('Run script "%1$s" on host groups', $scripts[$scriptid]['name']).': '
648							);
649							$result[$i][$j][] = [implode(', ', $host_group_list), BR()];
650						}
651						break;
652
653					case OPERATION_TYPE_HOST_ADD:
654						$result[$i][$j][] = [bold(_('Add host')), BR()];
655						break;
656
657					case OPERATION_TYPE_HOST_REMOVE:
658						$result[$i][$j][] = [bold(_('Remove host')), BR()];
659						break;
660
661					case OPERATION_TYPE_HOST_ENABLE:
662						$result[$i][$j][] = [bold(_('Enable host')), BR()];
663						break;
664
665					case OPERATION_TYPE_HOST_DISABLE:
666						$result[$i][$j][] = [bold(_('Disable host')), BR()];
667						break;
668
669					case OPERATION_TYPE_GROUP_ADD:
670					case OPERATION_TYPE_GROUP_REMOVE:
671						$host_group_list = [];
672
673						foreach ($operation['opgroup'] as $groupid) {
674							if (array_key_exists($groupid['groupid'], $host_groups)) {
675								$host_group_list[] = $host_groups[$groupid['groupid']]['name'];
676							}
677						}
678
679						order_result($host_group_list);
680
681						if ($operation['operationtype'] == OPERATION_TYPE_GROUP_ADD) {
682							$result[$i][$j][] = bold(_('Add to host groups').': ');
683						}
684						else {
685							$result[$i][$j][] = bold(_('Remove from host groups').': ');
686						}
687
688						$result[$i][$j][] = [implode(', ', $host_group_list), BR()];
689						break;
690
691					case OPERATION_TYPE_TEMPLATE_ADD:
692					case OPERATION_TYPE_TEMPLATE_REMOVE:
693						$template_list = [];
694
695						foreach ($operation['optemplate'] as $templateid) {
696							if (array_key_exists($templateid['templateid'], $templates)) {
697								$template_list[] = $templates[$templateid['templateid']]['name'];
698							}
699						}
700
701						order_result($template_list);
702
703						if ($operation['operationtype'] == OPERATION_TYPE_TEMPLATE_ADD) {
704							$result[$i][$j][] = bold(_('Link to templates').': ');
705						}
706						else {
707							$result[$i][$j][] = bold(_('Unlink from templates').': ');
708						}
709
710						$result[$i][$j][] = [implode(', ', $template_list), BR()];
711						break;
712
713					case OPERATION_TYPE_HOST_INVENTORY:
714						$host_inventory_modes = getHostInventoryModes();
715						$result[$i][$j][] = bold(operation_type2str(OPERATION_TYPE_HOST_INVENTORY).': ');
716						$result[$i][$j][] = [
717							$host_inventory_modes[$operation['opinventory']['inventory_mode']],
718							BR()
719						];
720						break;
721				}
722			}
723		}
724		else {
725			$operations_key = ($type == ACTION_RECOVERY_OPERATION)
726				? 'recovery_operations'
727				: 'ack_operations';
728
729			foreach ($action[$operations_key] as $j => $operation) {
730				switch ($operation['operationtype']) {
731					case OPERATION_TYPE_MESSAGE:
732						$media_type = _('all media');
733						$media_typeid = $operation['opmessage']['mediatypeid'];
734
735						if ($media_typeid != 0 && isset($media_types[$media_typeid])) {
736							$media_type = $media_types[$media_typeid]['name'];
737						}
738
739						if (array_key_exists('opmessage_usr', $operation) && $operation['opmessage_usr']) {
740							$user_names_list = [];
741
742							foreach ($operation['opmessage_usr'] as $user) {
743								if (isset($fullnames[$user['userid']])) {
744									$user_names_list[] = $fullnames[$user['userid']];
745								}
746							}
747
748							order_result($user_names_list);
749
750							$result[$i][$j][] = bold(_('Send message to users').': ');
751							$result[$i][$j][] = [implode(', ', $user_names_list), SPACE, _('via'), SPACE,
752								$media_type
753							];
754							$result[$i][$j][] = BR();
755						}
756
757						if (array_key_exists('opmessage_grp', $operation) && $operation['opmessage_grp']) {
758							$user_groups_list = [];
759
760							foreach ($operation['opmessage_grp'] as $userGroup) {
761								if (isset($user_groups[$userGroup['usrgrpid']])) {
762									$user_groups_list[] = $user_groups[$userGroup['usrgrpid']]['name'];
763								}
764							}
765
766							order_result($user_groups_list);
767
768							$result[$i][$j][] = bold(_('Send message to user groups').': ');
769							$result[$i][$j][] = [implode(', ', $user_groups_list), SPACE, _('via'), SPACE,
770								$media_type
771							];
772							$result[$i][$j][] = BR();
773						}
774						break;
775
776					case OPERATION_TYPE_COMMAND:
777						$scriptid = $operation['opcommand']['scriptid'];
778
779						if (array_key_exists('opcommand_hst', $operation) && $operation['opcommand_hst']) {
780							$host_list = [];
781
782							foreach ($operation['opcommand_hst'] as $host) {
783								if ($host['hostid'] == 0) {
784									$result[$i][$j][] = [
785										bold(_s('Run script "%1$s" on current host', $scripts[$scriptid]['name'])),
786										BR()
787									];
788								}
789								elseif (isset($hosts[$host['hostid']])) {
790									$host_list[] = $hosts[$host['hostid']]['name'];
791								}
792							}
793
794							if ($host_list) {
795								order_result($host_list);
796
797								$result[$i][$j][] = bold(
798									_s('Run script "%1$s" on hosts', $scripts[$scriptid]['name']).': '
799								);
800								$result[$i][$j][] = [implode(', ', $host_list), BR()];
801							}
802						}
803
804						if (array_key_exists('opcommand_grp', $operation) && $operation['opcommand_grp']) {
805							$host_group_list = [];
806
807							foreach ($operation['opcommand_grp'] as $host_group) {
808								if (isset($host_groups[$host_group['groupid']])) {
809									$host_group_list[] = $host_groups[$host_group['groupid']]['name'];
810								}
811							}
812
813							order_result($host_group_list);
814
815							$result[$i][$j][] = bold(
816								_s('Run script "%1$s" on host groups', $scripts[$scriptid]['name']).': '
817							);
818							$result[$i][$j][] = [implode(', ', $host_group_list), BR()];
819						}
820						break;
821
822					case OPERATION_TYPE_RECOVERY_MESSAGE:
823					case OPERATION_TYPE_ACK_MESSAGE:
824						$result[$i][$j][] = bold(_('Notify all involved'));
825						break;
826				}
827			}
828		}
829	}
830
831	return $result;
832}
833
834/**
835 * Return an array of action conditions supported by the given event source.
836 *
837 * @param int $eventsource
838 *
839 * @return mixed
840 */
841function get_conditions_by_eventsource($eventsource) {
842	$conditions[EVENT_SOURCE_TRIGGERS] = [
843		CONDITION_TYPE_TRIGGER_NAME,
844		CONDITION_TYPE_TRIGGER,
845		CONDITION_TYPE_TRIGGER_SEVERITY,
846		CONDITION_TYPE_HOST,
847		CONDITION_TYPE_HOST_GROUP,
848		CONDITION_TYPE_SUPPRESSED,
849		CONDITION_TYPE_EVENT_TAG,
850		CONDITION_TYPE_EVENT_TAG_VALUE,
851		CONDITION_TYPE_TEMPLATE,
852		CONDITION_TYPE_TIME_PERIOD
853	];
854	$conditions[EVENT_SOURCE_DISCOVERY] = [
855		CONDITION_TYPE_DHOST_IP,
856		CONDITION_TYPE_DCHECK,
857		CONDITION_TYPE_DOBJECT,
858		CONDITION_TYPE_DRULE,
859		CONDITION_TYPE_DSTATUS,
860		CONDITION_TYPE_PROXY,
861		CONDITION_TYPE_DVALUE,
862		CONDITION_TYPE_DSERVICE_PORT,
863		CONDITION_TYPE_DSERVICE_TYPE,
864		CONDITION_TYPE_DUPTIME
865	];
866	$conditions[EVENT_SOURCE_AUTOREGISTRATION] = [
867		CONDITION_TYPE_HOST_NAME,
868		CONDITION_TYPE_HOST_METADATA,
869		CONDITION_TYPE_PROXY
870	];
871	$conditions[EVENT_SOURCE_INTERNAL] = [
872		CONDITION_TYPE_EVENT_TYPE,
873		CONDITION_TYPE_HOST,
874		CONDITION_TYPE_HOST_GROUP,
875		CONDITION_TYPE_EVENT_TAG,
876		CONDITION_TYPE_EVENT_TAG_VALUE,
877		CONDITION_TYPE_TEMPLATE
878	];
879
880	if (isset($conditions[$eventsource])) {
881		return $conditions[$eventsource];
882	}
883
884	return $conditions[EVENT_SOURCE_TRIGGERS];
885}
886
887function get_opconditions_by_eventsource($eventsource) {
888	$conditions = [
889		EVENT_SOURCE_TRIGGERS => [CONDITION_TYPE_EVENT_ACKNOWLEDGED],
890		EVENT_SOURCE_DISCOVERY => []
891	];
892
893	if (isset($conditions[$eventsource])) {
894		return $conditions[$eventsource];
895	}
896}
897
898/**
899 * Return allowed operations types.
900 *
901 * @param int $eventsource
902 *
903 * @return array
904 */
905function getAllowedOperations($eventsource) {
906	if ($eventsource == EVENT_SOURCE_TRIGGERS) {
907		$operations = [
908			ACTION_OPERATION => [
909				OPERATION_TYPE_MESSAGE,
910				OPERATION_TYPE_COMMAND
911			],
912			ACTION_RECOVERY_OPERATION => [
913				OPERATION_TYPE_MESSAGE,
914				OPERATION_TYPE_COMMAND,
915				OPERATION_TYPE_RECOVERY_MESSAGE
916			],
917			ACTION_ACKNOWLEDGE_OPERATION => [
918				OPERATION_TYPE_MESSAGE,
919				OPERATION_TYPE_COMMAND,
920				OPERATION_TYPE_ACK_MESSAGE
921			]
922		];
923	}
924
925	if ($eventsource == EVENT_SOURCE_DISCOVERY) {
926		$operations[ACTION_OPERATION] = [
927			OPERATION_TYPE_MESSAGE,
928			OPERATION_TYPE_COMMAND,
929			OPERATION_TYPE_HOST_ADD,
930			OPERATION_TYPE_HOST_REMOVE,
931			OPERATION_TYPE_GROUP_ADD,
932			OPERATION_TYPE_GROUP_REMOVE,
933			OPERATION_TYPE_TEMPLATE_ADD,
934			OPERATION_TYPE_TEMPLATE_REMOVE,
935			OPERATION_TYPE_HOST_ENABLE,
936			OPERATION_TYPE_HOST_DISABLE,
937			OPERATION_TYPE_HOST_INVENTORY
938		];
939	}
940
941	if ($eventsource == EVENT_SOURCE_AUTOREGISTRATION) {
942		$operations[ACTION_OPERATION] = [
943			OPERATION_TYPE_MESSAGE,
944			OPERATION_TYPE_COMMAND,
945			OPERATION_TYPE_HOST_ADD,
946			OPERATION_TYPE_HOST_REMOVE,
947			OPERATION_TYPE_GROUP_ADD,
948			OPERATION_TYPE_GROUP_REMOVE,
949			OPERATION_TYPE_TEMPLATE_ADD,
950			OPERATION_TYPE_TEMPLATE_REMOVE,
951			OPERATION_TYPE_HOST_ENABLE,
952			OPERATION_TYPE_HOST_DISABLE,
953			OPERATION_TYPE_HOST_INVENTORY
954		];
955	}
956
957	if ($eventsource == EVENT_SOURCE_INTERNAL) {
958		$operations = [
959			ACTION_OPERATION => [OPERATION_TYPE_MESSAGE],
960			ACTION_RECOVERY_OPERATION => [
961				OPERATION_TYPE_MESSAGE,
962				OPERATION_TYPE_RECOVERY_MESSAGE
963			]
964		];
965	}
966
967	return $operations;
968}
969
970/**
971 * Get operation type text label according $type value. If $type is equal null array of all available operation types
972 * will be returned.
973 *
974 * @param int|null $type  Operation type, one of OPERATION_TYPE_* constant or null.
975 *
976 * @return string|array
977 */
978function operation_type2str($type) {
979	$types = [
980		OPERATION_TYPE_MESSAGE => _('Send message'),
981		OPERATION_TYPE_COMMAND => _('Remote command'),
982		OPERATION_TYPE_HOST_ADD => _('Add host'),
983		OPERATION_TYPE_HOST_REMOVE => _('Remove host'),
984		OPERATION_TYPE_HOST_ENABLE => _('Enable host'),
985		OPERATION_TYPE_HOST_DISABLE => _('Disable host'),
986		OPERATION_TYPE_GROUP_ADD => _('Add to host group'),
987		OPERATION_TYPE_GROUP_REMOVE => _('Remove from host group'),
988		OPERATION_TYPE_TEMPLATE_ADD => _('Link to template'),
989		OPERATION_TYPE_TEMPLATE_REMOVE => _('Unlink from template'),
990		OPERATION_TYPE_HOST_INVENTORY => _('Set host inventory mode'),
991		OPERATION_TYPE_RECOVERY_MESSAGE => _('Notify all involved'),
992		OPERATION_TYPE_ACK_MESSAGE => _('Notify all involved')
993	];
994
995	if (is_null($type)) {
996		return order_result($types);
997	}
998	elseif (isset($types[$type])) {
999		return $types[$type];
1000	}
1001	else {
1002		return _('Unknown');
1003	}
1004}
1005
1006function sortOperations($eventsource, &$operations) {
1007	if ($eventsource == EVENT_SOURCE_TRIGGERS || $eventsource == EVENT_SOURCE_INTERNAL) {
1008		$esc_step_from = [];
1009		$esc_step_to = [];
1010		$esc_period = [];
1011		$operationTypes = [];
1012
1013		$simple_interval_parser = new CSimpleIntervalParser();
1014
1015		foreach ($operations as $key => $operation) {
1016			$esc_step_from[$key] = $operation['esc_step_from'];
1017			$esc_step_to[$key] = $operation['esc_step_to'];
1018			// Try to sort by "esc_period" in seconds, otherwise sort as string in case it's a macro or something invalid.
1019			$esc_period[$key] = ($simple_interval_parser->parse($operation['esc_period']) == CParser::PARSE_SUCCESS)
1020				? timeUnitToSeconds($operation['esc_period'])
1021				: $operation['esc_period'];
1022
1023			$operationTypes[$key] = $operation['operationtype'];
1024		}
1025		array_multisort($esc_step_from, SORT_ASC, $esc_step_to, SORT_ASC, $esc_period, SORT_ASC, $operationTypes, SORT_ASC, $operations);
1026	}
1027	else {
1028		CArrayHelper::sort($operations, ['operationtype']);
1029	}
1030}
1031
1032/**
1033 * Return an array of operators supported by the given action condition.
1034 *
1035 * @param int $conditiontype
1036 *
1037 * @return array
1038 */
1039function get_operators_by_conditiontype($conditiontype) {
1040	$operators[CONDITION_TYPE_HOST_GROUP] = [
1041		CONDITION_OPERATOR_EQUAL,
1042		CONDITION_OPERATOR_NOT_EQUAL
1043	];
1044	$operators[CONDITION_TYPE_TEMPLATE] = [
1045		CONDITION_OPERATOR_EQUAL,
1046		CONDITION_OPERATOR_NOT_EQUAL
1047	];
1048	$operators[CONDITION_TYPE_HOST] = [
1049		CONDITION_OPERATOR_EQUAL,
1050		CONDITION_OPERATOR_NOT_EQUAL
1051	];
1052	$operators[CONDITION_TYPE_TRIGGER] = [
1053		CONDITION_OPERATOR_EQUAL,
1054		CONDITION_OPERATOR_NOT_EQUAL
1055	];
1056	$operators[CONDITION_TYPE_TRIGGER_NAME] = [
1057		CONDITION_OPERATOR_LIKE,
1058		CONDITION_OPERATOR_NOT_LIKE
1059	];
1060	$operators[CONDITION_TYPE_TRIGGER_SEVERITY] = [
1061		CONDITION_OPERATOR_EQUAL,
1062		CONDITION_OPERATOR_NOT_EQUAL,
1063		CONDITION_OPERATOR_MORE_EQUAL,
1064		CONDITION_OPERATOR_LESS_EQUAL
1065	];
1066	$operators[CONDITION_TYPE_TIME_PERIOD] = [
1067		CONDITION_OPERATOR_IN,
1068		CONDITION_OPERATOR_NOT_IN
1069	];
1070	$operators[CONDITION_TYPE_SUPPRESSED] = [
1071		CONDITION_OPERATOR_NO,
1072		CONDITION_OPERATOR_YES
1073	];
1074	$operators[CONDITION_TYPE_DRULE] = [
1075		CONDITION_OPERATOR_EQUAL,
1076		CONDITION_OPERATOR_NOT_EQUAL
1077	];
1078	$operators[CONDITION_TYPE_DCHECK] = [
1079		CONDITION_OPERATOR_EQUAL,
1080		CONDITION_OPERATOR_NOT_EQUAL
1081	];
1082	$operators[CONDITION_TYPE_DOBJECT] = [
1083		CONDITION_OPERATOR_EQUAL
1084	];
1085	$operators[CONDITION_TYPE_PROXY] = [
1086		CONDITION_OPERATOR_EQUAL,
1087		CONDITION_OPERATOR_NOT_EQUAL
1088	];
1089	$operators[CONDITION_TYPE_DHOST_IP] = [
1090		CONDITION_OPERATOR_EQUAL,
1091		CONDITION_OPERATOR_NOT_EQUAL
1092	];
1093	$operators[CONDITION_TYPE_DSERVICE_TYPE] = [
1094		CONDITION_OPERATOR_EQUAL,
1095		CONDITION_OPERATOR_NOT_EQUAL
1096	];
1097	$operators[CONDITION_TYPE_DSERVICE_PORT] = [
1098		CONDITION_OPERATOR_EQUAL,
1099		CONDITION_OPERATOR_NOT_EQUAL
1100	];
1101	$operators[CONDITION_TYPE_DSTATUS] = [
1102		CONDITION_OPERATOR_EQUAL
1103	];
1104	$operators[CONDITION_TYPE_DUPTIME] = [
1105		CONDITION_OPERATOR_MORE_EQUAL,
1106		CONDITION_OPERATOR_LESS_EQUAL
1107	];
1108	$operators[CONDITION_TYPE_DVALUE] = [
1109		CONDITION_OPERATOR_EQUAL,
1110		CONDITION_OPERATOR_NOT_EQUAL,
1111		CONDITION_OPERATOR_MORE_EQUAL,
1112		CONDITION_OPERATOR_LESS_EQUAL,
1113		CONDITION_OPERATOR_LIKE,
1114		CONDITION_OPERATOR_NOT_LIKE
1115	];
1116	$operators[CONDITION_TYPE_EVENT_ACKNOWLEDGED] = [
1117		CONDITION_OPERATOR_EQUAL
1118	];
1119	$operators[CONDITION_TYPE_HOST_NAME] = [
1120		CONDITION_OPERATOR_LIKE,
1121		CONDITION_OPERATOR_NOT_LIKE,
1122		CONDITION_OPERATOR_REGEXP,
1123		CONDITION_OPERATOR_NOT_REGEXP
1124	];
1125	$operators[CONDITION_TYPE_EVENT_TYPE] = [
1126		CONDITION_OPERATOR_EQUAL
1127	];
1128	$operators[CONDITION_TYPE_HOST_METADATA] = [
1129		CONDITION_OPERATOR_LIKE,
1130		CONDITION_OPERATOR_NOT_LIKE,
1131		CONDITION_OPERATOR_REGEXP,
1132		CONDITION_OPERATOR_NOT_REGEXP
1133	];
1134	$operators[CONDITION_TYPE_EVENT_TAG] = [
1135		CONDITION_OPERATOR_EQUAL,
1136		CONDITION_OPERATOR_NOT_EQUAL,
1137		CONDITION_OPERATOR_LIKE,
1138		CONDITION_OPERATOR_NOT_LIKE
1139	];
1140	$operators[CONDITION_TYPE_EVENT_TAG_VALUE] = [
1141		CONDITION_OPERATOR_EQUAL,
1142		CONDITION_OPERATOR_NOT_EQUAL,
1143		CONDITION_OPERATOR_LIKE,
1144		CONDITION_OPERATOR_NOT_LIKE
1145	];
1146
1147	if (isset($operators[$conditiontype])) {
1148		return $operators[$conditiontype];
1149	}
1150
1151	return [];
1152}
1153
1154function count_operations_delay($operations, $def_period) {
1155	$delays = [1 => 0];
1156	$periods = [];
1157	$max_step = 0;
1158
1159	$simple_interval_parser = new CSimpleIntervalParser();
1160
1161	$def_period = CMacrosResolverHelper::resolveTimeUnitMacros(
1162		[['def_period' => $def_period]], ['def_period']
1163	)[0]['def_period'];
1164
1165	$def_period = ($simple_interval_parser->parse($def_period) == CParser::PARSE_SUCCESS)
1166		? timeUnitToSeconds($def_period)
1167		: null;
1168
1169	$operations = CMacrosResolverHelper::resolveTimeUnitMacros($operations, ['esc_period']);
1170
1171	foreach ($operations as $operation) {
1172		$esc_period = ($simple_interval_parser->parse($operation['esc_period']) == CParser::PARSE_SUCCESS)
1173			? timeUnitToSeconds($operation['esc_period'])
1174			: null;
1175
1176		$esc_period = ($esc_period === null || $esc_period != 0) ? $esc_period : $def_period;
1177		$step_to = ($operation['esc_step_to'] != 0) ? $operation['esc_step_to'] : 9999;
1178
1179		if ($max_step < $operation['esc_step_from']) {
1180			$max_step = $operation['esc_step_from'];
1181		}
1182
1183		for ($i = $operation['esc_step_from']; $i <= $step_to; $i++) {
1184			if (!array_key_exists($i, $periods) || $esc_period === null || $periods[$i] > $esc_period) {
1185				$periods[$i] = $esc_period;
1186			}
1187		}
1188	}
1189
1190	for ($i = 1; $i <= $max_step; $i++) {
1191		$esc_period = array_key_exists($i, $periods) ? $periods[$i] : $def_period;
1192		$delays[$i + 1] = ($esc_period !== null && $delays[$i] !== null) ? $delays[$i] + $esc_period : null;
1193	}
1194
1195	return $delays;
1196}
1197
1198/**
1199 * Returns the names of the "Event type" action condition values.
1200 *
1201 * If the $type parameter is passed, returns the name of the specific value, otherwise - returns an array of all
1202 * supported values.
1203 *
1204 * @param string $type
1205 *
1206 * @return array|string
1207 */
1208function eventType($type = null) {
1209	$types = [
1210		EVENT_TYPE_ITEM_NOTSUPPORTED => _('Item in "not supported" state'),
1211		EVENT_TYPE_LLDRULE_NOTSUPPORTED => _('Low-level discovery rule in "not supported" state'),
1212		EVENT_TYPE_TRIGGER_UNKNOWN => _('Trigger in "unknown" state')
1213	];
1214
1215	if (is_null($type)) {
1216		return $types;
1217	}
1218
1219	return $types[$type];
1220}
1221
1222/**
1223 * Get data required to create messages, severity changes, actions icon with popup with event actions.
1224 *
1225 * @param array $events    Array with event objects with acknowledges.
1226 * @param array $triggers  Array of triggers.
1227 *
1228 * @return array
1229 */
1230function getEventsActionsIconsData(array $events, array $triggers) {
1231	$messages = getEventsMessages($events);
1232	$severities = getEventsSeverityChanges($events, $triggers);
1233	$actions = getEventsAlertsOverview($events);
1234
1235	return [
1236		'data' => [
1237			'messages' => $messages['data'],
1238			'severities' => $severities['data'],
1239			'actions' => $actions
1240		],
1241		'userids' => $messages['userids'] + $severities['userids']
1242	];
1243}
1244
1245/**
1246 * Get data, required to create messages icon with popup with event messages.
1247 *
1248 * @param array  $events                                 Array with event objects with acknowledges.
1249 * @param string $events[]['eventid']                    Problem event ID.
1250 * @param array  $events[]['acknowledges']               Array with manual updates to problem.
1251 * @param string $events[]['acknowledges'][]['action']   Action that was performed by problem update.
1252 * @param string $events[]['acknowledges'][]['message']  Message text.
1253 * @param string $events[]['acknowledges'][]['clock']    Time when message was added.
1254 * @param string $events[]['acknowledges'][]['userid']   Author's userid.
1255 *
1256 * @return array
1257 */
1258function getEventsMessages(array $events) {
1259	$messages = [];
1260	$userids = [];
1261
1262	// Create array of messages for each event
1263	foreach ($events as $event) {
1264		$event_messages = [];
1265
1266		foreach ($event['acknowledges'] as $ack) {
1267			if (($ack['action'] & ZBX_PROBLEM_UPDATE_MESSAGE) == ZBX_PROBLEM_UPDATE_MESSAGE) {
1268				$event_messages[] = [
1269					'message' => $ack['message'],
1270					'userid' => $ack['userid'],
1271					'clock' => $ack['clock']
1272				];
1273
1274				$userids[$ack['userid']] = true;
1275			}
1276		}
1277
1278		CArrayHelper::sort($event_messages, [['field' => 'clock', 'order' => ZBX_SORT_DOWN]]);
1279
1280		$messages[$event['eventid']] = [
1281			'messages' => array_values($event_messages),
1282			'count' => count($event_messages)
1283		];
1284	}
1285
1286	return [
1287		'data' => $messages,
1288		'userids' => $userids
1289	];
1290}
1291
1292/**
1293 * Get data, required to create severity changes icon with popup with event severity changes.
1294 *
1295 * @param array  $events                                      Array with event objects with acknowledges.
1296 * @param string $events[]['eventid']                         Problem event ID.
1297 * @param string $events[]['severity']                        Current event severity.
1298 * @param string $events[]['objectid']                        Related trigger ID.
1299 * @param array  $events[]['acknowledges']                    Array with manual updates to problem.
1300 * @param string $events[]['acknowledges'][]['action']        Action that was performed by problem update.
1301 * @param string $events[]['acknowledges'][]['clock']         Time when severity was changed.
1302 * @param string $events[]['acknowledges'][]['old_severity']  Severity before the change.
1303 * @param string $events[]['acknowledges'][]['new_severity']  Severity after the change.
1304 * @param string $events[]['acknowledges'][]['userid']        Responsible user's userid.
1305 * @param array  $triggers                                    Related trigger data.
1306 * @param string $triggers[]['priority']                      Severity of trigger.
1307 *
1308 * @return array
1309 */
1310function getEventsSeverityChanges(array $events, array $triggers) {
1311	$severities = [];
1312	$userids = [];
1313
1314	// Create array of messages for each event.
1315	foreach ($events as $event) {
1316		$event_severities = [];
1317
1318		foreach ($event['acknowledges'] as $ack) {
1319			if (($ack['action'] & ZBX_PROBLEM_UPDATE_SEVERITY) == ZBX_PROBLEM_UPDATE_SEVERITY) {
1320				$event_severities[] = [
1321					'old_severity' => $ack['old_severity'],
1322					'new_severity' => $ack['new_severity'],
1323					'userid' => $ack['userid'],
1324					'clock' => $ack['clock']
1325				];
1326
1327				$userids[$ack['userid']] = true;
1328			}
1329		}
1330
1331		CArrayHelper::sort($event_severities, [['field' => 'clock', 'order' => ZBX_SORT_DOWN]]);
1332
1333		$severities[$event['eventid']] = [
1334			'severities' => array_values($event_severities),
1335			'count' => count($event_severities),
1336			'original_severity' => $triggers[$event['objectid']]['priority'],
1337			'current_severity' => $event['severity']
1338		];
1339	}
1340
1341	return [
1342		'data' => $severities,
1343		'userids' => $userids
1344	];
1345}
1346
1347/**
1348 * Get data, required to create actions icon.
1349 *
1350 * @param array  $events                 Array with event objects with acknowledges.
1351 * @param string $events[]['eventid']    Problem event ID.
1352 * @param string $events[]['r_eventid']  OK event ID.
1353 *
1354 * @return array  List indexed by eventid containing overview on event alerts.
1355 */
1356function getEventsAlertsOverview(array $events): array {
1357	$alert_eventids = [];
1358	$actions = [];
1359	$event_alert_state = [];
1360
1361	foreach ($events as $event) {
1362		// Get alerts for event.
1363		$alert_eventids[$event['eventid']] = true;
1364
1365		// Get alerts for related recovery events.
1366		if ($event['r_eventid'] != 0) {
1367			$alert_eventids[$event['r_eventid']] = true;
1368		}
1369	}
1370
1371	if ($alert_eventids) {
1372		$event_alert_state = array_combine(array_keys($alert_eventids), array_fill(0, count($alert_eventids), [
1373			'failed_cnt' => 0,
1374			'in_progress_cnt' => 0,
1375			'total_cnt' => 0
1376		]));
1377
1378		$alerts = API::Alert()->get([
1379			'groupCount' => true,
1380			'countOutput' => true,
1381			'filter' => ['status' => ALERT_STATUS_FAILED],
1382			'eventids' => array_keys($alert_eventids)
1383		]);
1384
1385		foreach ($alerts as $alert) {
1386			$event_alert_state[$alert['eventid']]['failed_cnt'] = (int) $alert['rowscount'];
1387		}
1388
1389		$alerts = API::Alert()->get([
1390			'groupCount' => true,
1391			'countOutput' => true,
1392			'filter' => ['status' => [ALERT_STATUS_NEW, ALERT_STATUS_NOT_SENT]],
1393			'eventids' => array_keys($alert_eventids)
1394		]);
1395
1396		foreach ($alerts as $alert) {
1397			$event_alert_state[$alert['eventid']]['in_progress_cnt'] = (int) $alert['rowscount'];
1398		}
1399
1400		$alerts = API::Alert()->get([
1401			'groupCount' => true,
1402			'countOutput' => true,
1403			'eventids' => array_keys($alert_eventids)
1404		]);
1405
1406		foreach ($alerts as $alert) {
1407			$event_alert_state[$alert['eventid']]['total_cnt'] = (int) $alert['rowscount'];
1408		}
1409	}
1410
1411	// Create array of actions for each event.
1412	foreach ($events as $event) {
1413		$event_actions = $event_alert_state[$event['eventid']];
1414		if ($event['r_eventid']) {
1415			$r_event_actions = $event_alert_state[$event['r_eventid']];
1416			$event_actions['failed_cnt'] += $r_event_actions['failed_cnt'];
1417			$event_actions['total_cnt'] += $r_event_actions['total_cnt'];
1418			$event_actions['in_progress_cnt'] += $r_event_actions['in_progress_cnt'];
1419		}
1420
1421		$actions[$event['eventid']] = [
1422			'count' => $event_actions['total_cnt'] + count($event['acknowledges']),
1423			'has_uncomplete_action' => (bool) $event_actions['in_progress_cnt'],
1424			'has_failed_action' => (bool) $event_actions['failed_cnt']
1425		];
1426	}
1427
1428	return $actions;
1429}
1430
1431/**
1432 * Get data, required to create table with all (automatic and manual) actions for Event details page.
1433 *
1434 * @param array  $event               Event object with acknowledges.
1435 * @param string $event['eventid']    Problem event ID.
1436 * @param string $event['r_eventid']  OK event ID.
1437 *
1438 * @return array
1439 */
1440function getEventDetailsActions(array $event) {
1441	$r_events = [];
1442
1443	// Select eventids for alert retrieval.
1444	$alert_eventids = [$event['eventid']];
1445
1446	if ($event['r_eventid'] != 0) {
1447		$alert_eventids[] = $event['r_eventid'];
1448
1449		$r_events = API::Event()->get([
1450			'output' => ['clock'],
1451			'eventids' => $event['r_eventid'],
1452			'preservekeys' => true
1453		]);
1454	}
1455
1456	$search_limit = CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT);
1457
1458	// Get automatic actions (alerts).
1459	$alerts = API::Alert()->get([
1460		'output' => ['alerttype', 'clock', 'error', 'eventid', 'esc_step', 'mediatypeid', 'message', 'retries',
1461			'sendto', 'status', 'subject', 'userid', 'p_eventid', 'acknowledgeid'
1462		],
1463		'eventids' => $alert_eventids,
1464		'config' => $search_limit
1465	]);
1466
1467	$actions = getSingleEventActions($event, $r_events, $alerts);
1468
1469	return [
1470		'actions' => $actions['actions'],
1471		'mediatypeids' => $actions['mediatypeids'],
1472		'userids' => $actions['userids'],
1473		'count' => $actions['count']
1474	];
1475}
1476
1477/**
1478 * Get array with all actions for single event.
1479 *
1480 * @param array   $event                              Event objects with acknowledges.
1481 * @param string  $event['eventid']                   Problem event ID.
1482 * @param string  $event['r_eventid']                 OK event ID.
1483 * @param string  $event['clock']                     Time when event occurred.
1484 * @param array   $event['acknowledges']              Array with manual updates to problem.
1485 * @param string  $event['acknowledges'][]['userid']  User ID.
1486 * @param array   $r_events                           Recovery event data for all requested events.
1487 * @param string  $r_events[]['clock']                Recovery event creation time.
1488 * @param array   $alerts                             Alert data for all requested alerts.
1489 * @param string  $alerts[]['eventid']                If of problem event for which this alert was generated.
1490 * @param string  $alerts[]['mediatypeid']            ID for mediatype used for alert.
1491 * @param string  $alerts[]['alerttype']              Type of alert.
1492 * @param string  $alerts[]['status']                 Alert status.
1493 * @param string  $alerts[]['userid']                 ID of alert recipient.
1494 *
1495 * @return array
1496 */
1497function getSingleEventActions(array $event, array $r_events, array $alerts) {
1498	$action_count = 0;
1499	$has_uncomplete_action = false;
1500	$has_failed_action = false;
1501	$mediatypeids = [];
1502	$userids = [];
1503
1504	// Create array of automatically and manually performed actions combined.
1505	$actions = [];
1506
1507	// Add row for problem generation event.
1508	$actions[] = [
1509		'action_type' => ZBX_EVENT_HISTORY_PROBLEM_EVENT,
1510		'clock' => $event['clock']
1511	];
1512
1513	// Add row for problem recovery event.
1514	if (array_key_exists($event['r_eventid'], $r_events)) {
1515		$actions[] = [
1516			'action_type' => ZBX_EVENT_HISTORY_RECOVERY_EVENT,
1517			'clock' => $r_events[$event['r_eventid']]['clock']
1518		];
1519	}
1520
1521	// Add manual operations.
1522	foreach ($event['acknowledges'] as $ack) {
1523		$ack['action_type'] = ZBX_EVENT_HISTORY_MANUAL_UPDATE;
1524		$actions[] = $ack;
1525
1526		$action_count++;
1527		$userids[$ack['userid']] = true;
1528	}
1529
1530	// Add alerts.
1531	foreach ($alerts as $alert) {
1532		// Add only alerts, related to current event.
1533		if (bccomp($alert['eventid'], $event['eventid']) == 0
1534				|| bccomp($alert['eventid'], $event['r_eventid']) == 0) {
1535			$alert['action_type'] = ZBX_EVENT_HISTORY_ALERT;
1536			$actions[] = $alert;
1537
1538			$action_count++;
1539
1540			if ($alert['alerttype'] == ALERT_TYPE_MESSAGE) {
1541				if ($alert['mediatypeid'] != 0) {
1542					$mediatypeids[$alert['mediatypeid']] = true;
1543				}
1544
1545				if ($alert['userid'] != 0) {
1546					$userids[$alert['userid']] = true;
1547				}
1548			}
1549
1550			if ($alert['status'] == ALERT_STATUS_NEW || $alert['status'] == ALERT_STATUS_NOT_SENT) {
1551				$has_uncomplete_action = true;
1552			}
1553			elseif ($alert['status'] == ALERT_STATUS_FAILED) {
1554				$has_failed_action = true;
1555			}
1556		}
1557	}
1558
1559	// Sort by action_type is done to put Recovery event before actions, resulted from it. Same for other action_type.
1560	CArrayHelper::sort($actions, [
1561		['field' => 'clock', 'order' => ZBX_SORT_DOWN],
1562		['field' => 'action_type', 'order' => ZBX_SORT_DOWN]
1563	]);
1564
1565	return [
1566		'actions' => array_values($actions),
1567		'count' => $action_count,
1568		'has_uncomplete_action' => $has_uncomplete_action,
1569		'has_failed_action' => $has_failed_action,
1570		'mediatypeids' => $mediatypeids,
1571		'userids' => $userids
1572	];
1573}
1574
1575/**
1576 * Get data required to create history list in problem update page.
1577 *
1578 * @param array  $event                               Array with event objects with acknowledges.
1579 * @param array  $event['acknowledges']               Array with manual updates to problem.
1580 * @param string $event['acknowledges'][]['clock']    Time when severity was changed.
1581 * @param string $event['acknowledges'][]['userid']   Responsible user's userid.
1582 *
1583 * @return array
1584 */
1585function getEventUpdates(array $event) {
1586	$userids = [];
1587
1588	foreach ($event['acknowledges'] as $ack) {
1589		$userids[$ack['userid']] = true;
1590	}
1591
1592	CArrayHelper::sort($event['acknowledges'], [['field' => 'clock', 'order' => ZBX_SORT_DOWN]]);
1593
1594	return [
1595		'data' => array_values($event['acknowledges']),
1596		'userids' => $userids
1597	];
1598}
1599
1600/**
1601 * Make icons (messages, severity changes, actions) for actions column.
1602 *
1603 * @param string $eventid                Id for event, for which icons are created.
1604 * @param array  $actions                Array of actions data.
1605 * @param array  $actions['messages']    Messages icon data.
1606 * @param array  $actions['severities']  Severity change icon data.
1607 * @param array  $actions['actions']     Actions icon data.
1608 * @param array  $users                  User name, surname and username.
1609 *
1610 * @return CCol|string
1611 */
1612function makeEventActionsIcons($eventid, $actions, $users) {
1613	$messages_icon = makeEventMessagesIcon($actions['messages'][$eventid], $users);
1614	$severities_icon = makeEventSeverityChangesIcon($actions['severities'][$eventid], $users);
1615	$actions_icon = makeEventActionsIcon($actions['actions'][$eventid], $eventid);
1616
1617	$action_icons = [];
1618	if ($messages_icon !== null) {
1619		$action_icons[] = $messages_icon;
1620	}
1621	if ($severities_icon !== null) {
1622		$action_icons[] = $severities_icon;
1623	}
1624	if ($actions_icon !== null) {
1625		$action_icons[] = $actions_icon;
1626	}
1627
1628	return $action_icons ? (new CCol($action_icons))->addClass(ZBX_STYLE_NOWRAP) : '';
1629}
1630
1631/**
1632 * Create icon with hintbox for event messages.
1633 *
1634 * @param array  $data
1635 * @param array  $data['messages']               Array of messages.
1636 * @param string $data['messages'][]['message']  Message text.
1637 * @param string $data['messages'][]['clock']    Message creation time.
1638 * @param array  $users                          User name, surname and username.
1639 *
1640 * @return CButton|null
1641 */
1642function makeEventMessagesIcon(array $data, array $users): ?CButton {
1643	$total = $data['count'];
1644
1645	$table = (new CTableInfo())->setHeader([_('Time'), _('User'), _('Message')]);
1646
1647	for ($i = 0; $i < $total && $i < ZBX_WIDGET_ROWS; $i++) {
1648		$message = $data['messages'][$i];
1649
1650		// Added in order to reuse makeActionTableUser().
1651		$message['action_type'] = ZBX_EVENT_HISTORY_MANUAL_UPDATE;
1652
1653		$table->addRow([
1654			zbx_date2str(DATE_TIME_FORMAT_SECONDS, $message['clock']),
1655			makeActionTableUser($message, $users),
1656			zbx_nl2br($message['message'])
1657		]);
1658	}
1659
1660	return $total
1661		? makeActionIcon([
1662			'icon' => ZBX_STYLE_ACTION_ICON_MSGS,
1663			'button' => true,
1664			'hint' => [
1665				$table,
1666				($total > ZBX_WIDGET_ROWS)
1667					? (new CDiv(
1668						(new CDiv(
1669							(new CDiv(_s('Displaying %1$s of %2$s found', ZBX_WIDGET_ROWS, $total)))
1670								->addClass(ZBX_STYLE_TABLE_STATS)
1671						))->addClass(ZBX_STYLE_PAGING_BTN_CONTAINER)
1672					))->addClass(ZBX_STYLE_TABLE_PAGING)
1673					: null
1674			],
1675			'num' => $total,
1676			'aria-label' => _xn('%1$s message', '%1$s messages', $total, 'screen reader', $total)
1677		])
1678		: null;
1679}
1680
1681/**
1682 * Create icon with hintbox for event severity changes.
1683 *
1684 * @param array  $data
1685 * @param array  $data['severities']                    Array of severities.
1686 * @param string $data['severities'][]['old_severity']  Event severity before change.
1687 * @param string $data['severities'][]['new_severity']  Event severity after change.
1688 * @param string $data['severities'][]['clock']         Severity change time.
1689 * @param string $data['original_severity']             Severity before change.
1690 * @param string $data['current_severity']              Current severity.
1691 * @param int    $data['count']                         Total number of severity changes.
1692 * @param array  $users                                 User name, surname and username.
1693 *
1694 * @return CButton|null
1695 */
1696function makeEventSeverityChangesIcon(array $data, array $users): ?CButton {
1697	$total = $data['count'];
1698
1699	$table = (new CTableInfo())->setHeader([_('Time'), _('User'), _('Severity changes')]);
1700
1701	for ($i = 0; $i < $total && $i < ZBX_WIDGET_ROWS; $i++) {
1702		$severity = $data['severities'][$i];
1703
1704		// Added in order to reuse makeActionTableUser().
1705		$severity['action_type'] = ZBX_EVENT_HISTORY_MANUAL_UPDATE;
1706
1707		// severity changes
1708		$old_severity_name = getSeverityName($severity['old_severity']);
1709		$new_severity_name = getSeverityName($severity['new_severity']);
1710
1711		$table->addRow([
1712			zbx_date2str(DATE_TIME_FORMAT_SECONDS, $severity['clock']),
1713			makeActionTableUser($severity, $users),
1714			$old_severity_name.'&nbsp;&rArr;&nbsp;'.$new_severity_name
1715		]);
1716	}
1717
1718	// select icon
1719	if ($data['original_severity'] > $data['current_severity']) {
1720		$icon_style = ZBX_STYLE_ACTION_ICON_SEV_DOWN;
1721		$aria_label = _x('Severity decreased', 'screen reader');
1722	}
1723	elseif ($data['original_severity'] < $data['current_severity']) {
1724		$icon_style = ZBX_STYLE_ACTION_ICON_SEV_UP;
1725		$aria_label = _x('Severity increased', 'screen reader');
1726	}
1727	else {
1728		$icon_style = ZBX_STYLE_ACTION_ICON_SEV_CHANGED;
1729		$aria_label = _x('Severity changed', 'screen reader');
1730	}
1731
1732	return $total
1733		? makeActionIcon([
1734			'button' => true,
1735			'icon' => $icon_style,
1736			'hint' => [
1737				$table,
1738				($total > ZBX_WIDGET_ROWS)
1739					? (new CDiv(
1740						(new CDiv(
1741							(new CDiv(_s('Displaying %1$s of %2$s found', ZBX_WIDGET_ROWS, $total)))
1742								->addClass(ZBX_STYLE_TABLE_STATS)
1743						))->addClass(ZBX_STYLE_PAGING_BTN_CONTAINER)
1744					))->addClass(ZBX_STYLE_TABLE_PAGING)
1745					: null
1746			],
1747			'aria-label' => $aria_label
1748		])
1749		: null;
1750}
1751
1752/**
1753 * @param array  $actions                      Array with all actions sorted by clock.
1754 * @param int    $actions[]['action_type']     Type of action table entry (ZBX_EVENT_HISTORY_*).
1755 * @param string $actions[]['clock']           Time, when action was performed.
1756 * @param string $actions[]['message']         Message sent by alert, or written by manual update, or remote command text.
1757 * @param string $actions[]['action']          Flag with problem update operation performed (only for ZBX_EVENT_HISTORY_MANUAL_UPDATE).
1758 * @param string $actions[]['alerttype']       Type of alert (only for ZBX_EVENT_HISTORY_ALERT).
1759 * @param string $actions[]['mediatypeid']     Id for mediatype, where alert message was sent (only for ZBX_EVENT_HISTORY_ALERT).
1760 * @param string $actions[]['error']           Error message in case of failed alert (only for ZBX_EVENT_HISTORY_ALERT).
1761 * @param array  $users                        User name, surname and username.
1762 * @param array  $mediatypes                   Mediatypes with maxattempts value and name.
1763 * @param string $mediatypes[]['name']         Mediatype name.
1764 * @param string $mediatypes[]['maxattempts']  Maximum attempts for this mediatype.
1765 *
1766 * @return CTableInfo
1767 */
1768function makeEventActionsTable(array $actions, array $users, array $mediatypes): CTableInfo {
1769	$action_count = count($actions);
1770
1771	$table = (new CTableInfo())->setHeader([
1772		_('Time'), _('User/Recipient'), _('Action'), _('Message/Command'), _('Status'), _('Info')
1773	]);
1774
1775	for ($i = 0; $i < $action_count && $i < ZBX_WIDGET_ROWS; $i++) {
1776		$action = $actions[$i];
1777
1778		$message = '';
1779		if ($action['action_type'] == ZBX_EVENT_HISTORY_MANUAL_UPDATE
1780				&& ($action['action'] & ZBX_PROBLEM_UPDATE_MESSAGE) == ZBX_PROBLEM_UPDATE_MESSAGE) {
1781			$message = zbx_nl2br($action['message']);
1782		}
1783		elseif ($action['action_type'] == ZBX_EVENT_HISTORY_ALERT) {
1784			if ($action['alerttype'] == ALERT_TYPE_COMMAND) {
1785				$message = _('Remote command');
1786			}
1787			elseif ($action['alerttype'] == ALERT_TYPE_MESSAGE) {
1788				$message = array_key_exists($action['mediatypeid'], $mediatypes)
1789					? $mediatypes[$action['mediatypeid']]['name']
1790					: '';
1791			}
1792		}
1793
1794		$table->addRow([
1795			zbx_date2str(DATE_TIME_FORMAT_SECONDS, $action['clock']),
1796			makeActionTableUser($action, $users),
1797			makeActionTableIcon($action),
1798			$message,
1799			makeActionTableStatus($action),
1800			makeActionTableInfo($action, $mediatypes)
1801		]);
1802	}
1803
1804	return $table;
1805}
1806
1807/**
1808 * Create icon with hintbox for event actions.
1809 *
1810 * @param array  $data
1811 * @param int    $data['count']                     Number of actions.
1812 * @param bool   $data['has_uncomplete_action']     Does the event have at least one uncompleted alert action.
1813 * @param bool   $data['has_failed_action']         Does the event have at least one failed alert action.
1814 * @param string $eventid
1815 *
1816 * @return CButton|null
1817 */
1818function makeEventActionsIcon(array $data, $eventid): ?CButton {
1819	// Number of meaningful actions.
1820	$total = $data['count'];
1821
1822	// select icon
1823	if ($data['has_failed_action']) {
1824		$icon_style = ZBX_STYLE_ACTIONS_NUM_RED;
1825	}
1826	elseif ($data['has_uncomplete_action']) {
1827		$icon_style = ZBX_STYLE_ACTIONS_NUM_YELLOW;
1828	}
1829	else {
1830		$icon_style = ZBX_STYLE_ACTIONS_NUM_GRAY;
1831	}
1832
1833	return $total
1834		? makeActionIcon([
1835			'icon' => $icon_style,
1836			'button' => true,
1837			'num' => $total,
1838			'aria-label' => _xn('%1$s action', '%1$s actions', $total, 'screen reader', $total)
1839		])->setAjaxHint([
1840			'type' => 'eventactions',
1841			'data' => ['eventid' => $eventid]
1842		])
1843		: null;
1844}
1845
1846/**
1847 * Get table with list of event actions for event details page.
1848 *
1849 * @param array  $data
1850 * @param array  $data['actions']                     Array with all actions sorted by clock.
1851 * @param int    $data['actions'][]['action_type']    Type of action table entry (ZBX_EVENT_HISTORY_*).
1852 * @param string $data['actions'][]['clock']          Time, when action was performed.
1853 * @param string $data['actions'][]['message']        Message sent by alert, or written by manual update, or remote command text.
1854 * @param string $data['actions'][]['alerttype']      Type of alert (only for ZBX_EVENT_HISTORY_ALERT).
1855 * @param string $data['actions'][]['esc_step']       Alert escalation step (only for ZBX_EVENT_HISTORY_ALERT).
1856 * @param string $data['actions'][]['subject']        Message alert subject (only for ZBX_EVENT_HISTORY_ALERT).
1857 * @param string $data['actions'][]['p_eventid']      Problem eventid that was reason for alert (only for ZBX_EVENT_HISTORY_ALERT).
1858 * @param string $data['actions'][]['acknowledgeid']  Problem update action that was reason for alert (only for ZBX_EVENT_HISTORY_ALERT).
1859 * @param array  $users                               User name, surname and username.
1860 * @param array  $mediatypes                          Mediatypes with maxattempts value.
1861 *
1862 * @return CTableInfo
1863 */
1864function makeEventDetailsActionsTable(array $data, array $users, array $mediatypes) {
1865	$table = (new CTableInfo())->setHeader([
1866		_('Step'), _('Time'), _('User/Recipient'), _('Action'), _('Message/Command'), _('Status'), _('Info')
1867	]);
1868
1869	foreach ($data['actions'] as $action) {
1870		$esc_step = '';
1871
1872		if ($action['action_type'] == ZBX_EVENT_HISTORY_ALERT && $action['p_eventid'] == 0
1873				&& $action['acknowledgeid'] == 0) {
1874			/*
1875			 * Escalation step should be displayed, only if alert is caused by problem event.
1876			 * Escalation step should not be displayed, if alert is caused by resolve event, or by problem update.
1877			 */
1878			$esc_step = $action['esc_step'];
1879		}
1880
1881		$message = '';
1882		if ($action['action_type'] == ZBX_EVENT_HISTORY_ALERT && $action['alerttype'] == ALERT_TYPE_MESSAGE) {
1883			$message = [bold($action['subject']), BR(), BR(), zbx_nl2br($action['message'])];
1884		}
1885		elseif (($action['action_type'] == ZBX_EVENT_HISTORY_ALERT && $action['alerttype'] == ALERT_TYPE_COMMAND)
1886				|| $action['action_type'] == ZBX_EVENT_HISTORY_MANUAL_UPDATE) {
1887			$message = [
1888				bold(_('Command').':'),
1889				BR(),
1890				zbx_nl2br($action['message'])
1891			];
1892		}
1893
1894		$table->addRow([
1895			$esc_step,
1896			zbx_date2str(DATE_TIME_FORMAT_SECONDS, $action['clock']),
1897			makeEventDetailsTableUser($action, $users),
1898			makeActionTableIcon($action),
1899			$message,
1900			makeActionTableStatus($action),
1901			makeActionTableInfo($action, $mediatypes)
1902		]);
1903	}
1904
1905	return $table;
1906}
1907
1908/**
1909 * Get table with list of event updates for update event page.
1910 *
1911 * @param array  $actions                   Array with all actions sorted by clock.
1912 * @param string $actions[]['clock']        Time, when action was performed.
1913 * @param string $actions[]['message']      Message sent by alert, or written by manual update, or remote command text.
1914 * @param array  $users                     User name, surname and username.
1915 *
1916 * @return CTable
1917 */
1918function makeEventHistoryTable(array $actions, array $users) {
1919	$table = (new CTable())
1920		->addStyle('width: 100%;')
1921		->setHeader([_('Time'), _('User'), _('User action'), _('Message')]);
1922
1923	foreach ($actions as $action) {
1924		// Added in order to reuse makeActionTableUser() and makeActionTableIcon()
1925		$action['action_type'] = ZBX_EVENT_HISTORY_MANUAL_UPDATE;
1926
1927		$table->addRow([
1928			zbx_date2str(DATE_TIME_FORMAT_SECONDS, $action['clock']),
1929			makeActionTableUser($action, $users),
1930			makeActionTableIcon($action),
1931			(new CCol(zbx_nl2br($action['message'])))->addClass(ZBX_STYLE_TABLE_FORMS_OVERFLOW_BREAK)
1932		]);
1933	}
1934
1935	return $table;
1936}
1937
1938/**
1939 * Creates username for message author or alert receiver.
1940 *
1941 * @param array  $action                 Array of action data.
1942 * @param int    $action['action_type']  Type of event table action (ZBX_EVENT_HISTORY_*).
1943 * @param string $action['alerttype']    Type of alert.
1944 * @param string $action['userid']       ID of message author, or alert receiver.
1945 * @param array  $users                  Array with user data - username, name, surname.
1946 *
1947 * @return string
1948 */
1949function makeActionTableUser(array $action, array $users) {
1950	if (($action['action_type'] == ZBX_EVENT_HISTORY_ALERT && $action['alerttype'] == ALERT_TYPE_MESSAGE)
1951			|| $action['action_type'] == ZBX_EVENT_HISTORY_MANUAL_UPDATE) {
1952		return array_key_exists($action['userid'], $users)
1953			? getUserFullname($users[$action['userid']])
1954			: _('Inaccessible user');
1955	}
1956	else {
1957		return '';
1958	}
1959}
1960
1961/**
1962 * Creates username for message author or alert receiver. Also contains 'sendto' for message actions.
1963 *
1964 * @param array  $action
1965 * @param int    $action['action_type']  Type of event table action (ZBX_EVENT_HISTORY_*).
1966 * @param string $action['alerttype']    Type of alert.
1967 * @param array  $action['userid']       ID of message author, or alert receiver.
1968 * @param array  $action['sendto']       Receiver media address for automatic action.
1969 * @param array  $users                  Array with user data - username, name, surname.
1970 *
1971 * @return string
1972 */
1973function makeEventDetailsTableUser(array $action, array $users) {
1974	if ($action['action_type'] == ZBX_EVENT_HISTORY_ALERT && $action['alerttype'] == ALERT_TYPE_MESSAGE) {
1975		return array_key_exists($action['userid'], $users)
1976			? [getUserFullname($users[$action['userid']]), BR(), italic(zbx_nl2br($action['sendto']))]
1977			: _('Inaccessible user');
1978	}
1979	elseif ($action['action_type'] == ZBX_EVENT_HISTORY_MANUAL_UPDATE) {
1980		return array_key_exists($action['userid'], $users)
1981			? getUserFullname($users[$action['userid']])
1982			: _('Inaccessible user');
1983	}
1984	else {
1985		return '';
1986	}
1987}
1988
1989/**
1990 * Make appropriate icon for event action.
1991 *
1992 * @param array  $action                  Array with actions table data.
1993 * @param int    $action['action_type']   Type of action table entry (ZBX_EVENT_HISTORY_*).
1994 * @param int    $action['action']        Flag with problem update operation performed. (only for ZBX_EVENT_HISTORY_MANUAL_UPDATE)
1995 * @param int    $action['old_severity']  Severity before problem update. (only for ZBX_EVENT_HISTORY_MANUAL_UPDATE)
1996 * @param int    $action['new_severity']  Severity after problem update. (only for ZBX_EVENT_HISTORY_MANUAL_UPDATE)
1997 * @param int    $action['alerttype']     Type of alert. (only for ZBX_EVENT_HISTORY_ALERT)
1998 *
1999 * @return CSpan
2000 */
2001function makeActionTableIcon(array $action) {
2002	switch ($action['action_type']) {
2003		case ZBX_EVENT_HISTORY_PROBLEM_EVENT:
2004			return makeActionIcon(['icon' => ZBX_STYLE_PROBLEM_GENERATED, 'title' => _('Problem created')]);
2005
2006		case ZBX_EVENT_HISTORY_RECOVERY_EVENT:
2007			return makeActionIcon(['icon' => ZBX_STYLE_PROBLEM_RECOVERY, 'title' => _('Problem resolved')]);
2008
2009		case ZBX_EVENT_HISTORY_MANUAL_UPDATE:
2010			$action_icons = [];
2011
2012			if (($action['action'] & ZBX_PROBLEM_UPDATE_CLOSE) == ZBX_PROBLEM_UPDATE_CLOSE) {
2013				$action_icons[] = makeActionIcon([
2014					'icon' => ZBX_STYLE_ACTION_ICON_CLOSE,
2015					'title' => _('Manually closed')
2016				]);
2017			}
2018
2019			if (($action['action'] & ZBX_PROBLEM_UPDATE_ACKNOWLEDGE) == ZBX_PROBLEM_UPDATE_ACKNOWLEDGE) {
2020				$action_icons[] = makeActionIcon(['icon' => ZBX_STYLE_ACTION_ICON_ACK, 'title' => _('Acknowledged')]);
2021			}
2022
2023			if (($action['action'] & ZBX_PROBLEM_UPDATE_UNACKNOWLEDGE) == ZBX_PROBLEM_UPDATE_UNACKNOWLEDGE) {
2024				$action_icons[] = makeActionIcon(['icon' => ZBX_STYLE_ACTION_ICON_UNACK, 'title' => _('Unacknowledged')]);
2025			}
2026
2027			if (($action['action'] & ZBX_PROBLEM_UPDATE_MESSAGE) == ZBX_PROBLEM_UPDATE_MESSAGE) {
2028				$action_icons[] = makeActionIcon(['icon' => ZBX_STYLE_ACTION_ICON_MSG, 'title' => _('Message')]);
2029			}
2030
2031			if (($action['action'] & ZBX_PROBLEM_UPDATE_SEVERITY) == ZBX_PROBLEM_UPDATE_SEVERITY) {
2032				$action_type = ($action['new_severity'] > $action['old_severity'])
2033					? ZBX_STYLE_ACTION_ICON_SEV_UP
2034					: ZBX_STYLE_ACTION_ICON_SEV_DOWN;
2035
2036				$old_severity_name = getSeverityName($action['old_severity']);
2037				$new_severity_name = getSeverityName($action['new_severity']);
2038				$hint = $old_severity_name.'&nbsp;&rArr;&nbsp;'.$new_severity_name;
2039
2040				$action_icons[] = makeActionIcon(['button' => true, 'icon' => $action_type, 'hint' => $hint]);
2041			}
2042
2043			return (new CCol($action_icons))->addClass(ZBX_STYLE_NOWRAP);
2044
2045		case ZBX_EVENT_HISTORY_ALERT:
2046			$action_icon = ($action['alerttype'] == ALERT_TYPE_COMMAND)
2047					? ZBX_STYLE_ACTION_COMMAND
2048					: ZBX_STYLE_ACTION_MESSAGE;
2049			$title = ($action['alerttype'] == ALERT_TYPE_COMMAND)
2050				? _('Remote command')
2051				: _('Alert message');
2052
2053			return makeActionIcon(['icon' => $action_icon, 'title' => $title]);
2054	}
2055}
2056
2057/**
2058 * Creates span with alert status text.
2059 *
2060 * @param array  $action
2061 * @param int    $action['action_type']  Type of event table action (ZBX_EVENT_HISTORY_*).
2062 * @param string $action['status']       Alert status.
2063 * @param string $action['alerttype']    Type of alert.
2064 *
2065 * @return CSpan|string
2066 */
2067function makeActionTableStatus(array $action) {
2068	if ($action['action_type'] == ZBX_EVENT_HISTORY_ALERT) {
2069		switch ($action['status']) {
2070			case ALERT_STATUS_SENT:
2071				$status_label = ($action['alerttype'] == ALERT_TYPE_COMMAND)
2072					? _('Executed')
2073					: _('Sent');
2074				$status_color = ZBX_STYLE_GREEN;
2075				break;
2076
2077			case ALERT_STATUS_NEW:
2078			case ALERT_STATUS_NOT_SENT:
2079				$status_label = _('In progress');
2080				$status_color = ZBX_STYLE_YELLOW;
2081				break;
2082
2083			default:
2084				$status_label = _('Failed');
2085				$status_color = ZBX_STYLE_RED;
2086				break;
2087		}
2088
2089		return (new CSpan($status_label))->addClass($status_color);
2090	}
2091	else {
2092		return '';
2093	}
2094}
2095
2096/**
2097 * Creates div with alert info icons.
2098 *
2099 * @param array  $action
2100 * @param int    $action['action_type']        Type of event table action (ZBX_EVENT_HISTORY_*).
2101 * @param string $action['status']             Alert status.
2102 * @param string $action['alerttype']          Type of alert.
2103 * @param string $action['mediatypeid']        ID for mediatype, where alert message was sent.
2104 * @param string $action['retries']            How many retries was done for pending alert message.
2105 * @param array  $mediatypes                   Array of media type data.
2106 * @param array  $mediatypes[]['maxattempts']  Maximum attempts for this mediatype.
2107 *
2108 * @return CDiv|string
2109 */
2110function makeActionTableInfo(array $action, array $mediatypes) {
2111	if ($action['action_type'] == ZBX_EVENT_HISTORY_ALERT) {
2112		$info_icons = [];
2113
2114		if ($action['alerttype'] == ALERT_TYPE_MESSAGE
2115				&& ($action['status'] == ALERT_STATUS_NEW || $action['status'] == ALERT_STATUS_NOT_SENT)) {
2116			$info_icons[] = makeWarningIcon(array_key_exists($action['mediatypeid'], $mediatypes)
2117				? _n('%1$s retry left', '%1$s retries left',
2118						$mediatypes[$action['mediatypeid']]['maxattempts'] - $action['retries']
2119				)
2120				: ''
2121			);
2122		}
2123		elseif ($action['error'] !== '') {
2124			$info_icons[] = makeErrorIcon($action['error']);
2125		}
2126
2127		return makeInformationList($info_icons);
2128	}
2129	else {
2130		return '';
2131	}
2132}
2133