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 * Class containing methods for operations with actions.
24 */
25class CAction extends CApiService {
26
27	public const ACCESS_RULES = [
28		'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER],
29		'create' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
30		'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
31		'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
32		'validateoperationsintegrity' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN]
33	];
34
35	protected $tableName = 'actions';
36	protected $tableAlias = 'a';
37	protected $sortColumns = ['actionid', 'name', 'status'];
38
39	/**
40	 * Valid condition types for each event source.
41	 *
42	 * @var array
43	 */
44	protected $valid_condition_types = [
45		EVENT_SOURCE_TRIGGERS => [
46			CONDITION_TYPE_HOST_GROUP, CONDITION_TYPE_HOST, CONDITION_TYPE_TRIGGER, CONDITION_TYPE_TRIGGER_NAME,
47			CONDITION_TYPE_TRIGGER_SEVERITY, CONDITION_TYPE_TIME_PERIOD, CONDITION_TYPE_TEMPLATE,
48			CONDITION_TYPE_SUPPRESSED, CONDITION_TYPE_EVENT_TAG, CONDITION_TYPE_EVENT_TAG_VALUE
49		],
50		EVENT_SOURCE_DISCOVERY => [
51			CONDITION_TYPE_DHOST_IP, CONDITION_TYPE_DSERVICE_TYPE, CONDITION_TYPE_DSERVICE_PORT, CONDITION_TYPE_DSTATUS,
52			CONDITION_TYPE_DUPTIME, CONDITION_TYPE_DVALUE, CONDITION_TYPE_DRULE, CONDITION_TYPE_DCHECK,
53			CONDITION_TYPE_PROXY, CONDITION_TYPE_DOBJECT
54		],
55		EVENT_SOURCE_AUTOREGISTRATION => [
56			CONDITION_TYPE_PROXY, CONDITION_TYPE_HOST_NAME, CONDITION_TYPE_HOST_METADATA
57		],
58		EVENT_SOURCE_INTERNAL => [
59			CONDITION_TYPE_HOST_GROUP, CONDITION_TYPE_HOST, CONDITION_TYPE_TEMPLATE, CONDITION_TYPE_EVENT_TYPE,
60			CONDITION_TYPE_EVENT_TAG, CONDITION_TYPE_EVENT_TAG_VALUE
61		]
62	];
63
64	/**
65	 * Valid operators for each condition type.
66	 *
67	 * @var array
68	 */
69	protected $valid_condition_type_operators = [
70		CONDITION_TYPE_HOST_GROUP => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL],
71		CONDITION_TYPE_HOST => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL],
72		CONDITION_TYPE_TRIGGER => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL],
73		CONDITION_TYPE_TRIGGER_NAME => [CONDITION_OPERATOR_LIKE, CONDITION_OPERATOR_NOT_LIKE],
74		CONDITION_TYPE_TRIGGER_SEVERITY => [
75			CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL, CONDITION_OPERATOR_MORE_EQUAL,
76			CONDITION_OPERATOR_LESS_EQUAL
77		],
78		CONDITION_TYPE_TIME_PERIOD => [CONDITION_OPERATOR_IN, CONDITION_OPERATOR_NOT_IN],
79		CONDITION_TYPE_DHOST_IP => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL],
80		CONDITION_TYPE_DSERVICE_TYPE => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL],
81		CONDITION_TYPE_DSERVICE_PORT => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL],
82		CONDITION_TYPE_DSTATUS => [CONDITION_OPERATOR_EQUAL],
83		CONDITION_TYPE_DUPTIME => [CONDITION_OPERATOR_MORE_EQUAL, CONDITION_OPERATOR_LESS_EQUAL],
84		CONDITION_TYPE_DVALUE => [
85			CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL, CONDITION_OPERATOR_LIKE,
86			CONDITION_OPERATOR_NOT_LIKE, CONDITION_OPERATOR_MORE_EQUAL, CONDITION_OPERATOR_LESS_EQUAL
87		],
88		CONDITION_TYPE_TEMPLATE => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL],
89		CONDITION_TYPE_SUPPRESSED => [CONDITION_OPERATOR_YES, CONDITION_OPERATOR_NO],
90		CONDITION_TYPE_DRULE => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL],
91		CONDITION_TYPE_DCHECK => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL],
92		CONDITION_TYPE_PROXY => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL],
93		CONDITION_TYPE_DOBJECT => [CONDITION_OPERATOR_EQUAL],
94		CONDITION_TYPE_HOST_NAME => [CONDITION_OPERATOR_LIKE, CONDITION_OPERATOR_NOT_LIKE, CONDITION_OPERATOR_REGEXP,
95			CONDITION_OPERATOR_NOT_REGEXP
96		],
97		CONDITION_TYPE_EVENT_TYPE => [CONDITION_OPERATOR_EQUAL],
98		CONDITION_TYPE_HOST_METADATA => [CONDITION_OPERATOR_LIKE, CONDITION_OPERATOR_NOT_LIKE,
99			CONDITION_OPERATOR_REGEXP, CONDITION_OPERATOR_NOT_REGEXP
100		],
101		CONDITION_TYPE_EVENT_TAG => [
102			CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL, CONDITION_OPERATOR_LIKE, CONDITION_OPERATOR_NOT_LIKE
103		],
104		CONDITION_TYPE_EVENT_TAG_VALUE => [
105			CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL, CONDITION_OPERATOR_LIKE, CONDITION_OPERATOR_NOT_LIKE
106		]
107	];
108
109	/**
110	 * Get actions data
111	 *
112	 * @param array $options
113	 * @param array $options['itemids']
114	 * @param array $options['hostids']
115	 * @param array $options['groupids']
116	 * @param array $options['actionids']
117	 * @param array $options['status']
118	 * @param bool  $options['editable']
119	 * @param array $options['extendoutput']
120	 * @param array $options['count']
121	 * @param array $options['pattern']
122	 * @param array $options['limit']
123	 * @param array $options['order']
124	 *
125	 * @return array|int item data as array or false if error
126	 */
127	public function get($options = []) {
128		$result = [];
129
130		$sqlParts = [
131			'select'	=> ['actions' => 'a.actionid'],
132			'from'		=> ['actions' => 'actions a'],
133			'where'		=> [],
134			'order'		=> [],
135			'limit'		=> null
136		];
137
138		$defOptions = [
139			'groupids'						=> null,
140			'hostids'						=> null,
141			'actionids'						=> null,
142			'triggerids'					=> null,
143			'mediatypeids'					=> null,
144			'usrgrpids'						=> null,
145			'userids'						=> null,
146			'scriptids'						=> null,
147			'nopermissions'					=> null,
148			'editable'						=> false,
149			// filter
150			'filter'					=> null,
151			'search'					=> null,
152			'searchByAny'				=> null,
153			'startSearch'				=> false,
154			'excludeSearch'				=> false,
155			'searchWildcardsEnabled'	=> null,
156			// output
157			'output'					=> API_OUTPUT_EXTEND,
158			'selectFilter'				=> null,
159			'selectOperations'			=> null,
160			'selectRecoveryOperations'	=> null,
161			'selectAcknowledgeOperations'	=> null,
162			'countOutput'				=> false,
163			'preservekeys'				=> false,
164			'sortfield'					=> '',
165			'sortorder'					=> '',
166			'limit'						=> null
167		];
168		$options = zbx_array_merge($defOptions, $options);
169
170		// editable + PERMISSION CHECK
171		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) {
172			// conditions are checked here by sql, operations after, by api queries
173			$permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ;
174			$userGroups = getUserGroupsByUserId(self::$userData['userid']);
175
176			// condition hostgroup
177			$sqlParts['where'][] = 'NOT EXISTS ('.
178					'SELECT NULL'.
179					' FROM conditions cc'.
180						' LEFT JOIN rights r'.
181							' ON r.id='.zbx_dbcast_2bigint('cc.value').
182								' AND '.dbConditionInt('r.groupid', $userGroups).
183					' WHERE a.actionid=cc.actionid'.
184						' AND cc.conditiontype='.CONDITION_TYPE_HOST_GROUP.
185					' GROUP BY cc.value'.
186					' HAVING MIN(r.permission) IS NULL'.
187						' OR MIN(r.permission)='.PERM_DENY.
188						' OR MAX(r.permission)<'.zbx_dbstr($permission).
189					')';
190
191			// condition host or template
192			$sqlParts['where'][] = 'NOT EXISTS ('.
193					'SELECT NULL'.
194					' FROM conditions cc,hosts_groups hgg'.
195						' LEFT JOIN rights r'.
196							' ON r.id=hgg.groupid'.
197								' AND '.dbConditionInt('r.groupid', $userGroups).
198					' WHERE a.actionid=cc.actionid'.
199						' AND '.zbx_dbcast_2bigint('cc.value').'=hgg.hostid'.
200						' AND cc.conditiontype IN ('.CONDITION_TYPE_HOST.','.CONDITION_TYPE_TEMPLATE.')'.
201					' GROUP BY cc.value'.
202					' HAVING MIN(r.permission) IS NULL'.
203						' OR MIN(r.permission)='.PERM_DENY.
204						' OR MAX(r.permission)<'.zbx_dbstr($permission).
205					')';
206
207			// condition trigger
208			$sqlParts['where'][] = 'NOT EXISTS ('.
209					'SELECT NULL'.
210					' FROM conditions cc,functions f,items i,hosts_groups hgg'.
211						' LEFT JOIN rights r'.
212							' ON r.id=hgg.groupid'.
213								' AND '.dbConditionInt('r.groupid', $userGroups).
214					' WHERE a.actionid=cc.actionid'.
215						' AND '.zbx_dbcast_2bigint('cc.value').'=f.triggerid'.
216						' AND f.itemid=i.itemid'.
217						' AND i.hostid=hgg.hostid'.
218						' AND cc.conditiontype='.CONDITION_TYPE_TRIGGER.
219					' GROUP BY cc.value'.
220					' HAVING MIN(r.permission) IS NULL'.
221						' OR MIN(r.permission)='.PERM_DENY.
222						' OR MAX(r.permission)<'.zbx_dbstr($permission).
223					')';
224		}
225
226		// actionids
227		if (!is_null($options['actionids'])) {
228			zbx_value2array($options['actionids']);
229
230			$sqlParts['where'][] = dbConditionInt('a.actionid', $options['actionids']);
231		}
232
233		// groupids
234		if (!is_null($options['groupids'])) {
235			zbx_value2array($options['groupids']);
236
237			$sqlParts['from']['conditions_groups'] = 'conditions cg';
238			$sqlParts['where'][] = dbConditionString('cg.value', $options['groupids']);
239			$sqlParts['where']['ctg'] = 'cg.conditiontype='.CONDITION_TYPE_HOST_GROUP;
240			$sqlParts['where']['acg'] = 'a.actionid=cg.actionid';
241		}
242
243		// hostids
244		if (!is_null($options['hostids'])) {
245			zbx_value2array($options['hostids']);
246
247			$sqlParts['from']['conditions_hosts'] = 'conditions ch';
248			$sqlParts['where'][] = dbConditionString('ch.value', $options['hostids']);
249			$sqlParts['where']['cth'] = 'ch.conditiontype='.CONDITION_TYPE_HOST;
250			$sqlParts['where']['ach'] = 'a.actionid=ch.actionid';
251		}
252
253		// triggerids
254		if (!is_null($options['triggerids'])) {
255			zbx_value2array($options['triggerids']);
256
257			$sqlParts['from']['conditions_triggers'] = 'conditions ct';
258			$sqlParts['where'][] = dbConditionString('ct.value', $options['triggerids']);
259			$sqlParts['where']['ctt'] = 'ct.conditiontype='.CONDITION_TYPE_TRIGGER;
260			$sqlParts['where']['act'] = 'a.actionid=ct.actionid';
261		}
262
263		// mediatypeids
264		if (!is_null($options['mediatypeids'])) {
265			zbx_value2array($options['mediatypeids']);
266
267			$sqlParts['from']['opmessage'] = 'opmessage om';
268			$sqlParts['from']['operations_media'] = 'operations omed';
269			$sqlParts['where'][] = dbConditionId('om.mediatypeid', $options['mediatypeids']);
270			$sqlParts['where']['aomed'] = 'a.actionid=omed.actionid';
271			$sqlParts['where']['oom'] = 'omed.operationid=om.operationid';
272		}
273
274		// operation messages
275		// usrgrpids
276		if (!is_null($options['usrgrpids'])) {
277			zbx_value2array($options['usrgrpids']);
278
279			$sqlParts['from']['opmessage_grp'] = 'opmessage_grp omg';
280			$sqlParts['from']['operations_usergroups'] = 'operations oug';
281			$sqlParts['where'][] = dbConditionInt('omg.usrgrpid', $options['usrgrpids']);
282			$sqlParts['where']['aoug'] = 'a.actionid=oug.actionid';
283			$sqlParts['where']['oomg'] = 'oug.operationid=omg.operationid';
284		}
285
286		// userids
287		if (!is_null($options['userids'])) {
288			zbx_value2array($options['userids']);
289
290			$sqlParts['from']['opmessage_usr'] = 'opmessage_usr omu';
291			$sqlParts['from']['operations_users'] = 'operations ou';
292			$sqlParts['where'][] = dbConditionInt('omu.userid', $options['userids']);
293			$sqlParts['where']['aou'] = 'a.actionid=ou.actionid';
294			$sqlParts['where']['oomu'] = 'ou.operationid=omu.operationid';
295		}
296
297		// operation commands
298		// scriptids
299		if (!is_null($options['scriptids'])) {
300			zbx_value2array($options['scriptids']);
301
302			$sqlParts['from']['opcommand'] = 'opcommand oc';
303			$sqlParts['from']['operations_scripts'] = 'operations os';
304			$sqlParts['where'][] = dbConditionInt('oc.scriptid', $options['scriptids']);
305			$sqlParts['where']['aos'] = 'a.actionid=os.actionid';
306			$sqlParts['where']['ooc'] = 'os.operationid=oc.operationid';
307		}
308
309		// filter
310		if (is_array($options['filter'])) {
311			if (array_key_exists('esc_period', $options['filter']) && $options['filter']['esc_period'] !== null) {
312				$options['filter']['esc_period'] = getTimeUnitFilters($options['filter']['esc_period']);
313			}
314
315			$this->dbFilter('actions a', $options, $sqlParts);
316		}
317
318		// search
319		if (is_array($options['search'])) {
320			zbx_db_search('actions a', $options, $sqlParts);
321		}
322
323		// limit
324		if (zbx_ctype_digit($options['limit']) && $options['limit']) {
325			$sqlParts['limit'] = $options['limit'];
326		}
327
328		$actionIds = [];
329
330		$sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
331		$sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
332		$dbRes = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']);
333		while ($action = DBfetch($dbRes)) {
334			if ($options['countOutput']) {
335				$result = $action['rowscount'];
336			}
337			else {
338				$actionIds[$action['actionid']] = $action['actionid'];
339
340				$result[$action['actionid']] = $action;
341			}
342		}
343
344		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) {
345			// check hosts, templates
346			$hosts = [];
347			$hostIds = [];
348			$sql = 'SELECT o.actionid,och.hostid'.
349					' FROM operations o,opcommand_hst och'.
350					' WHERE o.operationid=och.operationid'.
351						' AND och.hostid<>0'.
352						' AND '.dbConditionInt('o.actionid', $actionIds);
353			$dbHosts = DBselect($sql);
354			while ($host = DBfetch($dbHosts)) {
355				if (!isset($hosts[$host['hostid']])) {
356					$hosts[$host['hostid']] = [];
357				}
358				$hosts[$host['hostid']][$host['actionid']] = $host['actionid'];
359				$hostIds[$host['hostid']] = $host['hostid'];
360			}
361
362			$dbTemplates = DBselect(
363				'SELECT o.actionid,ot.templateid'.
364				' FROM operations o,optemplate ot'.
365				' WHERE o.operationid=ot.operationid'.
366					' AND '.dbConditionInt('o.actionid', $actionIds)
367			);
368			while ($template = DBfetch($dbTemplates)) {
369				if (!isset($hosts[$template['templateid']])) {
370					$hosts[$template['templateid']] = [];
371				}
372				$hosts[$template['templateid']][$template['actionid']] = $template['actionid'];
373				$hostIds[$template['templateid']] = $template['templateid'];
374			}
375
376			$allowedHosts = API::Host()->get([
377				'hostids' => $hostIds,
378				'output' => ['hostid'],
379				'editable' => $options['editable'],
380				'templated_hosts' => true,
381				'preservekeys' => true
382			]);
383			foreach ($hostIds as $hostId) {
384				if (isset($allowedHosts[$hostId])) {
385					continue;
386				}
387				foreach ($hosts[$hostId] as $actionId) {
388					unset($result[$actionId], $actionIds[$actionId]);
389				}
390			}
391			unset($allowedHosts);
392
393			// check hostgroups
394			$groups = [];
395			$groupIds = [];
396			$dbGroups = DBselect(
397				'SELECT o.actionid,ocg.groupid'.
398				' FROM operations o,opcommand_grp ocg'.
399				' WHERE o.operationid=ocg.operationid'.
400					' AND '.dbConditionInt('o.actionid', $actionIds)
401			);
402			while ($group = DBfetch($dbGroups)) {
403				if (!isset($groups[$group['groupid']])) {
404					$groups[$group['groupid']] = [];
405				}
406				$groups[$group['groupid']][$group['actionid']] = $group['actionid'];
407				$groupIds[$group['groupid']] = $group['groupid'];
408			}
409
410			$dbGroups = DBselect(
411				'SELECT o.actionid,og.groupid'.
412				' FROM operations o,opgroup og'.
413				' WHERE o.operationid=og.operationid'.
414					' AND '.dbConditionInt('o.actionid', $actionIds)
415			);
416			while ($group = DBfetch($dbGroups)) {
417				if (!isset($groups[$group['groupid']])) {
418					$groups[$group['groupid']] = [];
419				}
420				$groups[$group['groupid']][$group['actionid']] = $group['actionid'];
421				$groupIds[$group['groupid']] = $group['groupid'];
422			}
423
424			$allowedGroups = API::HostGroup()->get([
425				'groupids' => $groupIds,
426				'output' => ['groupid'],
427				'editable' => $options['editable'],
428				'preservekeys' => true
429			]);
430			foreach ($groupIds as $groupId) {
431				if (isset($allowedGroups[$groupId])) {
432					continue;
433				}
434				foreach ($groups[$groupId] as $actionId) {
435					unset($result[$actionId], $actionIds[$actionId]);
436				}
437			}
438			unset($allowedGroups);
439
440			// check scripts
441			$scripts = [];
442			$scriptIds = [];
443			$dbScripts = DBselect(
444				'SELECT o.actionid,oc.scriptid'.
445				' FROM operations o,opcommand oc'.
446				' WHERE o.operationid=oc.operationid'.
447					' AND '.dbConditionInt('o.actionid', $actionIds)
448			);
449			while ($script = DBfetch($dbScripts)) {
450				if (!isset($scripts[$script['scriptid']])) {
451					$scripts[$script['scriptid']] = [];
452				}
453				$scripts[$script['scriptid']][$script['actionid']] = $script['actionid'];
454				$scriptIds[$script['scriptid']] = $script['scriptid'];
455			}
456
457			$allowedScripts = API::Script()->get([
458				'output' => ['scriptid'],
459				'scriptids' => $scriptIds,
460				'filter' => ['scope' => ZBX_SCRIPT_SCOPE_ACTION],
461				'preservekeys' => true
462			]);
463			foreach ($scriptIds as $scriptId) {
464				if (isset($allowedScripts[$scriptId])) {
465					continue;
466				}
467				foreach ($scripts[$scriptId] as $actionId) {
468					unset($result[$actionId], $actionIds[$actionId]);
469				}
470			}
471			unset($allowedScripts);
472
473			// check users
474			$users = [];
475			$userIds = [];
476			$dbUsers = DBselect(
477				'SELECT o.actionid,omu.userid'.
478				' FROM operations o,opmessage_usr omu'.
479				' WHERE o.operationid=omu.operationid'.
480					' AND '.dbConditionInt('o.actionid', $actionIds)
481			);
482			while ($user = DBfetch($dbUsers)) {
483				if (!isset($users[$user['userid']])) {
484					$users[$user['userid']] = [];
485				}
486				$users[$user['userid']][$user['actionid']] = $user['actionid'];
487				$userIds[$user['userid']] = $user['userid'];
488			}
489
490			$allowedUsers = API::User()->get([
491				'userids' => $userIds,
492				'output' => ['userid'],
493				'preservekeys' => true
494			]);
495			foreach ($userIds as $userId) {
496				if (isset($allowedUsers[$userId])) {
497					continue;
498				}
499				foreach ($users[$userId] as $actionId) {
500					unset($result[$actionId], $actionIds[$actionId]);
501				}
502			}
503
504			// check usergroups
505			$userGroups = [];
506			$userGroupIds = [];
507			$dbUserGroups = DBselect(
508				'SELECT o.actionid,omg.usrgrpid'.
509				' FROM operations o,opmessage_grp omg'.
510				' WHERE o.operationid=omg.operationid'.
511					' AND '.dbConditionInt('o.actionid', $actionIds)
512			);
513			while ($userGroup = DBfetch($dbUserGroups)) {
514				if (!isset($userGroups[$userGroup['usrgrpid']])) {
515					$userGroups[$userGroup['usrgrpid']] = [];
516				}
517				$userGroups[$userGroup['usrgrpid']][$userGroup['actionid']] = $userGroup['actionid'];
518				$userGroupIds[$userGroup['usrgrpid']] = $userGroup['usrgrpid'];
519			}
520
521			$allowedUserGroups = API::UserGroup()->get([
522				'usrgrpids' => $userGroupIds,
523				'output' => ['usrgrpid'],
524				'preservekeys' => true
525			]);
526
527			foreach ($userGroupIds as $userGroupId) {
528				if (isset($allowedUserGroups[$userGroupId])) {
529					continue;
530				}
531				foreach ($userGroups[$userGroupId] as $actionId) {
532					unset($result[$actionId], $actionIds[$actionId]);
533				}
534			}
535		}
536
537		if ($options['countOutput']) {
538			return $result;
539		}
540
541		if ($result) {
542			$result = $this->addRelatedObjects($options, $result);
543
544			foreach ($result as &$action) {
545				// unset the fields that are returned in the filter
546				unset($action['formula'], $action['evaltype']);
547
548				if ($options['selectFilter'] !== null) {
549					$filter = $this->unsetExtraFields(
550						[$action['filter']],
551						['conditions', 'formula', 'evaltype'],
552						$options['selectFilter']
553					);
554					$filter = reset($filter);
555
556					if (isset($filter['conditions'])) {
557						foreach ($filter['conditions'] as &$condition) {
558							unset($condition['actionid'], $condition['conditionid']);
559						}
560						unset($condition);
561					}
562
563					$action['filter'] = $filter;
564				}
565			}
566			unset($action);
567		}
568
569		// removing keys (hash -> array)
570		if (!$options['preservekeys']) {
571			$result = zbx_cleanHashes($result);
572		}
573
574		return $result;
575	}
576
577	/**
578	 * Add actions.
579	 *
580	 * @param array $actions multidimensional array with actions data
581	 * @param array $actions[0,...]['expression']
582	 * @param array $actions[0,...]['description']
583	 * @param array $actions[0,...]['type'] OPTIONAL
584	 * @param array $actions[0,...]['priority'] OPTIONAL
585	 * @param array $actions[0,...]['status'] OPTIONAL
586	 * @param array $actions[0,...]['comments'] OPTIONAL
587	 * @param array $actions[0,...]['url'] OPTIONAL
588	 * @param array $actions[0,...]['filter'] OPTIONAL
589	 * @param array $actions[0,...]['pause_suppressed'] OPTIONAL
590	 *
591	 * @return array
592	 */
593	public function create($actions) {
594		$actions = zbx_toArray($actions);
595
596		$this->validateCreate($actions);
597
598		// Set "evaltype" if specified in "filter" section of action.
599		foreach ($actions as &$action) {
600			if (isset($action['filter'])) {
601				$action['evaltype'] = $action['filter']['evaltype'];
602			}
603
604			// Set default values for recovery operations and their messages.
605			if (array_key_exists('recovery_operations', $action)) {
606				foreach ($action['recovery_operations'] as &$operation) {
607					if ($operation['operationtype'] == OPERATION_TYPE_MESSAGE
608							|| $operation['operationtype'] == OPERATION_TYPE_RECOVERY_MESSAGE) {
609						$message = (array_key_exists('opmessage', $operation) && is_array($operation['opmessage']))
610							? $operation['opmessage']
611							: [];
612
613						$operation['opmessage'] = $message + [
614							'default_msg' => 1,
615							'mediatypeid' => 0,
616							'subject' => '',
617							'message' => ''
618						];
619					}
620				}
621				unset($operation);
622			}
623
624			// Set default values for acknowledge operations and their messages.
625			if (array_key_exists('acknowledge_operations', $action)) {
626				foreach ($action['acknowledge_operations'] as &$operation) {
627					if ($operation['operationtype'] == OPERATION_TYPE_MESSAGE
628							|| $operation['operationtype'] == OPERATION_TYPE_ACK_MESSAGE) {
629						$message = (array_key_exists('opmessage', $operation) && is_array($operation['opmessage']))
630							? $operation['opmessage']
631							: [];
632
633						$operation['opmessage'] = $message + [
634							'default_msg' => 1,
635							'mediatypeid' => 0,
636							'subject' => '',
637							'message' => ''
638						];
639					}
640				}
641				unset($operation);
642			}
643		}
644		unset($action);
645
646		// Insert actions into db, get back array with new actionids.
647		$actions = DB::save('actions', $actions);
648		$actions = zbx_toHash($actions, 'actionid');
649		$audit = [];
650
651		$conditions_to_create = [];
652		$operations_to_create = [];
653
654		// Collect conditions and operations to be created and set appropriate action ID.
655		foreach ($actions as $actionid => $action) {
656			$audit[] = ['actionid' => $actionid, 'name' => $action['name']];
657
658			if (isset($action['filter'])) {
659				foreach ($action['filter']['conditions'] as $condition) {
660					$condition['actionid'] = $actionid;
661					$conditions_to_create[] = $condition;
662				}
663			}
664
665			if (array_key_exists('operations', $action) && $action['operations']) {
666				foreach ($action['operations'] as $operation) {
667					$operation['actionid'] = $actionid;
668					$operation['recovery'] = ACTION_OPERATION;
669					$operations_to_create[] = $operation;
670				}
671			}
672
673			if (array_key_exists('recovery_operations', $action) && $action['recovery_operations']) {
674				foreach ($action['recovery_operations'] as $recovery_operation) {
675					$recovery_operation['actionid'] = $actionid;
676					$recovery_operation['recovery'] = ACTION_RECOVERY_OPERATION;
677					unset($recovery_operation['esc_period'], $recovery_operation['esc_step_from'],
678						$recovery_operation['esc_step_to']
679					);
680
681					if ($recovery_operation['operationtype'] == OPERATION_TYPE_RECOVERY_MESSAGE) {
682						unset($recovery_operation['opmessage']['mediatypeid']);
683					}
684
685					$operations_to_create[] = $recovery_operation;
686				}
687			}
688
689			if (array_key_exists('acknowledge_operations', $action) && $action['acknowledge_operations']) {
690				foreach ($action['acknowledge_operations'] as $ack_operation) {
691					$ack_operation['actionid'] = $actionid;
692					$ack_operation['recovery'] = ACTION_ACKNOWLEDGE_OPERATION;
693					unset($ack_operation['esc_period'], $ack_operation['esc_step_from'], $ack_operation['esc_step_to']);
694					$operations_to_create[] = $ack_operation;
695				}
696			}
697		}
698
699		$createdConditions = $this->addConditions($conditions_to_create);
700
701		// Group back created action conditions by action ID to be used for updating action formula.
702		$conditionsForActions = [];
703		foreach ($createdConditions as $condition) {
704			$conditionsForActions[$condition['actionid']][$condition['conditionid']] = $condition;
705		}
706
707		// Update "formula" field if evaltype is custom expression.
708		foreach ($actions as $actionid => $action) {
709			if (isset($action['filter'])) {
710				$actionFilter = $action['filter'];
711				if ($actionFilter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
712					$this->updateFormula($actionid, $actionFilter['formula'], $conditionsForActions[$actionid]);
713				}
714			}
715		}
716
717		// Add operations.
718		$this->addOperations($operations_to_create);
719
720		$this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_ACTION, $audit);
721
722		return ['actionids' => array_keys($actions)];
723	}
724
725	/**
726	 * Update actions.
727	 *
728	 * @param array $actions multidimensional array with actions data
729	 * @param array $actions[0,...]['actionid']
730	 * @param array $actions[0,...]['expression']
731	 * @param array $actions[0,...]['description']
732	 * @param array $actions[0,...]['type'] OPTIONAL
733	 * @param array $actions[0,...]['priority'] OPTIONAL
734	 * @param array $actions[0,...]['status'] OPTIONAL
735	 * @param array $actions[0,...]['comments'] OPTIONAL
736	 * @param array $actions[0,...]['url'] OPTIONAL
737	 * @param array $actions[0,...]['filter'] OPTIONAL
738	 * @param array $actions[0,...]['pause_suppressed'] OPTIONAL
739	 *
740	 * @return array
741	 */
742	public function update($actions) {
743		$actions = zbx_toArray($actions);
744		$actions = zbx_toHash($actions, 'actionid');
745		$actionIds = array_keys($actions);
746
747		$db_actions = $this->get([
748			'output' => API_OUTPUT_EXTEND,
749			'selectFilter' => ['formula', 'conditions'],
750			'selectOperations' => API_OUTPUT_EXTEND,
751			'selectRecoveryOperations' => API_OUTPUT_EXTEND,
752			'selectAcknowledgeOperations' => ['operationid', 'actionid', 'operationtype', 'opmessage', 'opmessage_grp',
753				'opmessage_usr', 'opcommand', 'opcommand_hst', 'opcommand_grp'
754			],
755			'actionids' => $actionIds,
756			'editable' => true,
757			'preservekeys' => true
758		]);
759
760		$this->validateUpdate($actions, $db_actions);
761
762		$operations_to_create = [];
763		$operations_to_update = [];
764		$operationids_to_delete = [];
765
766		$actions_update_data = [];
767
768		$newActionConditions = null;
769		foreach ($actions as $actionId => $action) {
770			$db_action = $db_actions[$actionId];
771
772			$actionUpdateValues = $action;
773			unset(
774				$actionUpdateValues['actionid'],
775				$actionUpdateValues['filter'],
776				$actionUpdateValues['operations'],
777				$actionUpdateValues['recovery_operations'],
778				$actionUpdateValues['acknowledge_operations'],
779				$actionUpdateValues['conditions'],
780				$actionUpdateValues['formula'],
781				$actionUpdateValues['evaltype']
782			);
783
784			if (isset($action['filter'])) {
785				$actionFilter = $action['filter'];
786
787				// set formula to empty string of not custom expression
788				if ($actionFilter['evaltype'] != CONDITION_EVAL_TYPE_EXPRESSION) {
789					$actionUpdateValues['formula'] = '';
790				}
791
792				$actionUpdateValues['evaltype'] = $actionFilter['evaltype'];
793			}
794
795			if (array_key_exists('operations', $action)) {
796				$db_operations = zbx_toHash($db_action['operations'], 'operationid');
797
798				foreach ($action['operations'] as $operation) {
799					if (!array_key_exists('operationid', $operation)) {
800						$operation['actionid'] = $action['actionid'];
801						$operation['recovery'] = ACTION_OPERATION;
802						$operations_to_create[] = $operation;
803					}
804					else {
805						$operationid = $operation['operationid'];
806
807						if (array_key_exists($operationid, $db_operations)) {
808							$operation['recovery'] = ACTION_OPERATION;
809							$operations_to_update[] = $operation;
810							unset($db_operations[$operationid]);
811						}
812						else {
813							self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value "%1$s" for "%2$s" field.',
814								$operationid, 'operationid'
815							));
816						}
817					}
818				}
819				$operationids_to_delete = array_merge($operationids_to_delete, array_keys($db_operations));
820			}
821
822			if (array_key_exists('recovery_operations', $action)) {
823				$db_recovery_operations = zbx_toHash($db_action['recoveryOperations'], 'operationid');
824
825				foreach ($action['recovery_operations'] as $recovery_operation) {
826					unset($recovery_operation['esc_period'], $recovery_operation['esc_step_from'],
827						$recovery_operation['esc_step_to']
828					);
829					$recovery_operation['actionid'] = $action['actionid'];
830
831					if (!array_key_exists('operationid', $recovery_operation)) {
832						if ($recovery_operation['operationtype'] == OPERATION_TYPE_RECOVERY_MESSAGE) {
833							unset($recovery_operation['opmessage']['mediatypeid']);
834						}
835
836						$recovery_operation['recovery'] = ACTION_RECOVERY_OPERATION;
837						$operations_to_create[] = $recovery_operation;
838					}
839					else {
840						$recovery_operationid = $recovery_operation['operationid'];
841
842						if (array_key_exists($recovery_operationid, $db_recovery_operations)) {
843							$db_operation_type = $db_recovery_operations[$recovery_operationid]['operationtype'];
844							if ((array_key_exists('operationtype', $recovery_operation)
845									&& $recovery_operation['operationtype'] == OPERATION_TYPE_RECOVERY_MESSAGE)
846									|| (!array_key_exists('operationtype', $recovery_operation)
847										&& $db_operation_type == OPERATION_TYPE_RECOVERY_MESSAGE)) {
848								unset($recovery_operation['opmessage']['mediatypeid']);
849							}
850
851							$recovery_operation['recovery'] = ACTION_RECOVERY_OPERATION;
852							$operations_to_update[] = $recovery_operation;
853							unset($db_recovery_operations[$recovery_operationid]);
854						}
855						else {
856							self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value "%1$s" for "%2$s" field.',
857								$recovery_operationid, 'operationid'
858							));
859						}
860					}
861				}
862				$operationids_to_delete = array_merge($operationids_to_delete, array_keys($db_recovery_operations));
863			}
864
865			if (array_key_exists('acknowledge_operations', $action)) {
866				$db_ack_operations = zbx_toHash($db_action['acknowledgeOperations'], 'operationid');
867
868				foreach ($action['acknowledge_operations'] as $ack_operation) {
869					$ack_operation['recovery'] = ACTION_ACKNOWLEDGE_OPERATION;
870					$opmessage = (array_key_exists('opmessage', $ack_operation) && is_array($ack_operation['opmessage']))
871						? $ack_operation['opmessage']
872						: [];
873					unset($ack_operation['esc_period'], $ack_operation['esc_step_from'], $ack_operation['esc_step_to']);
874					$ack_operation['actionid'] = $action['actionid'];
875
876					if (!array_key_exists('operationid', $ack_operation)) {
877						if ($ack_operation['operationtype'] == OPERATION_TYPE_MESSAGE
878								|| $ack_operation['operationtype'] == OPERATION_TYPE_ACK_MESSAGE) {
879							$ack_operation['opmessage'] += [
880								'default_msg' => 1,
881								'mediatypeid' => 0,
882								'subject' => '',
883								'message' => ''
884							];
885						}
886
887						$operations_to_create[] = $ack_operation;
888					}
889					elseif (array_key_exists($ack_operation['operationid'], $db_ack_operations)) {
890						if ($ack_operation['operationtype'] == OPERATION_TYPE_MESSAGE
891								|| $ack_operation['operationtype'] == OPERATION_TYPE_ACK_MESSAGE) {
892							$db_opmessage = array_key_exists('opmessage', $db_ack_operations[$ack_operation['operationid']])
893								? $db_ack_operations[$ack_operation['operationid']]['opmessage']
894								: [
895									'default_msg' => 1,
896									'mediatypeid' => 0,
897									'subject' => '',
898									'message' => ''
899								];
900							$default_msg = array_key_exists('default_msg', $opmessage)
901								? $opmessage['default_msg']
902								: $db_opmessage['default_msg'];
903
904							if ($default_msg == 1) {
905								$opmessage['subject'] = '';
906								$opmessage['message'] = '';
907								$ack_operation['opmessage'] = $opmessage;
908							}
909						}
910
911						$operations_to_update[] = $ack_operation;
912						unset($db_ack_operations[$ack_operation['operationid']]);
913					}
914					else {
915						self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value "%1$s" for "%2$s" field.',
916							$ack_operation['operationid'], 'operationid'
917						));
918					}
919				}
920				$operationids_to_delete = array_merge($operationids_to_delete, array_keys($db_ack_operations));
921			}
922
923			if ($actionUpdateValues) {
924				$actions_update_data[] = ['values' => $actionUpdateValues, 'where' => ['actionid' => $actionId]];
925			}
926		}
927
928		if ($actions_update_data) {
929			DB::update('actions', $actions_update_data);
930			$this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_ACTION, $actions, $db_actions);
931		}
932
933		// add, update and delete operations
934		$this->addOperations($operations_to_create);
935		$this->updateOperations($operations_to_update, $db_actions);
936		if (!empty($operationids_to_delete)) {
937			$this->deleteOperations($operationids_to_delete);
938		}
939
940		// set actionid for all conditions and group by actionid into $newActionConditions
941		$newActionConditions = null;
942		foreach ($actions as $actionId => $action) {
943			if (isset($action['filter'])) {
944				if ($newActionConditions === null) {
945					$newActionConditions = [];
946				}
947
948				$newActionConditions[$actionId] = [];
949				foreach ($action['filter']['conditions'] as $condition) {
950					$condition['actionid'] = $actionId;
951					$newActionConditions[$actionId][] = $condition;
952				}
953			}
954		}
955
956		// if we have any conditions, fetch current conditions from db and do replace by position and group result
957		// by actionid into $actionConditions
958		$actionConditions = [];
959		if ($newActionConditions !== null) {
960			$existingConditions = DBfetchArray(DBselect(
961				'SELECT conditionid,actionid,conditiontype,operator,value,value2'.
962				' FROM conditions'.
963				' WHERE '.dbConditionInt('actionid', $actionIds).
964				' ORDER BY conditionid'
965			));
966			$existingActionConditions = [];
967			foreach ($existingConditions as $condition) {
968				$existingActionConditions[$condition['actionid']][] = $condition;
969			}
970
971			$conditions = DB::replaceByPosition('conditions', $existingActionConditions, $newActionConditions);
972			foreach ($conditions as $condition) {
973				$actionConditions[$condition['actionid']][] = $condition;
974			}
975		}
976
977		// update formulas for user expressions using new conditions
978		foreach ($actions as $actionId => $action) {
979			if (isset($action['filter']) && $action['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
980				$this->updateFormula($actionId, $action['filter']['formula'], $actionConditions[$actionId]);
981			}
982		}
983
984		return ['actionids' => $actionIds];
985	}
986
987	/**
988	 * @param array $conditions
989	 *
990	 * @return mixed
991	 */
992	protected function addConditions($conditions) {
993		foreach ($conditions as $condition) {
994			$connectionDbFields = [
995				'actionid' => null,
996				'conditiontype' => null
997			];
998			if (!check_db_fields($connectionDbFields, $condition)) {
999				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect parameters for condition.'));
1000			}
1001		}
1002
1003		return DB::save('conditions', $conditions);
1004	}
1005
1006	protected function updateConditions($conditions) {
1007		$update = [];
1008		foreach ($conditions as $condition) {
1009			$conditionId = $condition['conditionid'];
1010			unset($condition['conditionid']);
1011			$update = [
1012				'values' => $condition,
1013				'where' => ['conditionid' => $conditionId]
1014			];
1015		}
1016		DB::update('conditions', $update);
1017
1018		return $conditions;
1019	}
1020
1021	protected function deleteConditions($conditionids) {
1022		DB::delete('conditions', ['conditionid' => $conditionids]);
1023	}
1024
1025	/**
1026	 * @param array $operations
1027	 *
1028	 * @return bool
1029	 */
1030	protected function addOperations($operations) {
1031		foreach ($operations as $operation) {
1032			$operationDbFields = [
1033				'actionid' => null,
1034				'operationtype' => null
1035			];
1036			if (!check_db_fields($operationDbFields, $operation)) {
1037				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect parameter for operations.'));
1038			}
1039		}
1040
1041		$operations = DB::save('operations', $operations);
1042		$operations = zbx_toHash($operations, 'operationid');
1043
1044		$opMessagesToInsert = [];
1045		$opCommandsToInsert = [];
1046		$msggroups_to_insert = [];
1047		$msgusers_to_insert = [];
1048		$opCommandHstsToInsert = [];
1049		$opCommandGroupInserts = [];
1050		$opGroupsToInsert = [];
1051		$opTemplatesToInsert = [];
1052		$opConditionsToInsert = [];
1053		$opInventoryToInsert = [];
1054
1055		foreach ($operations as $operationId => $operation) {
1056			switch ($operation['operationtype']) {
1057				case OPERATION_TYPE_MESSAGE:
1058					if (isset($operation['opmessage']) && !empty($operation['opmessage'])) {
1059						$operation['opmessage']['operationid'] = $operationId;
1060						$opMessagesToInsert[] = $operation['opmessage'];
1061					}
1062					if (isset($operation['opmessage_usr'])) {
1063						foreach ($operation['opmessage_usr'] as $user) {
1064							$msgusers_to_insert[] = [
1065								'operationid' => $operationId,
1066								'userid' => $user['userid']
1067							];
1068						}
1069					}
1070					if (isset($operation['opmessage_grp'])) {
1071						foreach ($operation['opmessage_grp'] as $userGroup) {
1072							$msggroups_to_insert[] = [
1073								'operationid' => $operationId,
1074								'usrgrpid' => $userGroup['usrgrpid']
1075							];
1076						}
1077					}
1078					break;
1079
1080				case OPERATION_TYPE_COMMAND:
1081					if (isset($operation['opcommand']) && !empty($operation['opcommand'])) {
1082						$operation['opcommand']['operationid'] = $operationId;
1083						$opCommandsToInsert[] = $operation['opcommand'];
1084					}
1085					if (isset($operation['opcommand_hst'])) {
1086						foreach ($operation['opcommand_hst'] as $host) {
1087							$opCommandHstsToInsert[] = [
1088								'operationid' => $operationId,
1089								'hostid' => $host['hostid']
1090							];
1091						}
1092					}
1093					if (isset($operation['opcommand_grp'])) {
1094						foreach ($operation['opcommand_grp'] as $hostGroup) {
1095							$opCommandGroupInserts[] = [
1096								'operationid' => $operationId,
1097								'groupid' => $hostGroup['groupid']
1098							];
1099						}
1100					}
1101					break;
1102
1103				case OPERATION_TYPE_GROUP_ADD:
1104				case OPERATION_TYPE_GROUP_REMOVE:
1105					foreach ($operation['opgroup'] as $hostGroup) {
1106						$opGroupsToInsert[] = [
1107							'operationid' => $operationId,
1108							'groupid' => $hostGroup['groupid']
1109						];
1110					}
1111					break;
1112
1113				case OPERATION_TYPE_TEMPLATE_ADD:
1114				case OPERATION_TYPE_TEMPLATE_REMOVE:
1115					foreach ($operation['optemplate'] as $template) {
1116						$opTemplatesToInsert[] = [
1117							'operationid' => $operationId,
1118							'templateid' => $template['templateid']
1119						];
1120					}
1121					break;
1122
1123				case OPERATION_TYPE_HOST_INVENTORY:
1124					$opInventoryToInsert[] = [
1125						'operationid' => $operationId,
1126						'inventory_mode' => $operation['opinventory']['inventory_mode']
1127					];
1128					break;
1129
1130				case OPERATION_TYPE_ACK_MESSAGE:
1131					// falls through
1132				case OPERATION_TYPE_RECOVERY_MESSAGE:
1133					if (array_key_exists('opmessage', $operation) && $operation['opmessage']) {
1134						$operation['opmessage']['operationid'] = $operationId;
1135						$opMessagesToInsert[] = $operation['opmessage'];
1136					}
1137					break;
1138			}
1139			if (isset($operation['opconditions'])) {
1140				foreach ($operation['opconditions'] as $opCondition) {
1141					$opCondition['operationid'] = $operationId;
1142					$opConditionsToInsert[] = $opCondition;
1143				}
1144			}
1145		}
1146
1147		DB::insert('opmessage_grp', $msggroups_to_insert);
1148		DB::insert('opmessage_usr', $msgusers_to_insert);
1149		DB::insert('opconditions', $opConditionsToInsert);
1150		DB::insert('opmessage', $opMessagesToInsert, false);
1151		DB::insert('opcommand', $opCommandsToInsert, false);
1152		DB::insert('opcommand_hst', $opCommandHstsToInsert);
1153		DB::insert('opcommand_grp', $opCommandGroupInserts);
1154		DB::insert('opgroup', $opGroupsToInsert);
1155		DB::insert('optemplate', $opTemplatesToInsert);
1156		DB::insert('opinventory', $opInventoryToInsert, false);
1157
1158		return true;
1159	}
1160
1161	/**
1162	 * @param array $operations
1163	 * @param array $db_actions
1164	 */
1165	protected function updateOperations($operations, $db_actions) {
1166		$operationsUpdate = [];
1167
1168		// messages
1169		$opMessagesToInsert = [];
1170		$opMessagesToUpdate = [];
1171		$opMessagesToDeleteByOpId = [];
1172
1173		$opMessageGrpsToInsert = [];
1174		$opMessageUsrsToInsert = [];
1175		$opMessageGrpsToDeleteByOpId = [];
1176		$opMessageUsrsToDeleteByOpId = [];
1177
1178		// commands
1179		$opCommandsToInsert = [];
1180		$opCommandsToUpdate = [];
1181		$opCommandsToDeleteByOpId = [];
1182
1183		$opCommandGrpsToInsert = [];
1184		$opCommandHstsToInsert = [];
1185		$opCommandGrpsToDeleteByOpId = [];
1186		$opCommandHstsToDeleteByOpId = [];
1187
1188		// groups
1189		$opGroupsToInsert = [];
1190		$opGroupsToDeleteByOpId = [];
1191
1192		// templates
1193		$opTemplateToInsert = [];
1194		$opTemplatesToDeleteByOpId = [];
1195
1196		// operation conditions
1197		$opConditionsToInsert = [];
1198
1199		// inventory
1200		$opInventoryToInsert = [];
1201		$opInventoryToUpdate = [];
1202		$opInventoryToDeleteByOpId = [];
1203
1204		$operation_actions_hashkey = [
1205			ACTION_OPERATION				=> 'operations',
1206			ACTION_RECOVERY_OPERATION		=> 'recoveryOperations',
1207			ACTION_ACKNOWLEDGE_OPERATION	=> 'acknowledgeOperations'
1208		];
1209
1210		foreach ($operations as $operation) {
1211			$actions_key = $operation_actions_hashkey[$operation['recovery']];
1212
1213			$operationsDb = zbx_toHash($db_actions[$operation['actionid']][$actions_key], 'operationid');
1214
1215			$operationDb = $operationsDb[$operation['operationid']];
1216
1217			$type_changed = false;
1218
1219			if (isset($operation['operationtype']) && ($operation['operationtype'] != $operationDb['operationtype'])) {
1220				$type_changed = true;
1221
1222				switch ($operationDb['operationtype']) {
1223					case OPERATION_TYPE_MESSAGE:
1224						$opMessagesToDeleteByOpId[] = $operationDb['operationid'];
1225						$opMessageGrpsToDeleteByOpId[] = $operationDb['operationid'];
1226						$opMessageUsrsToDeleteByOpId[] = $operationDb['operationid'];
1227						break;
1228
1229					case OPERATION_TYPE_COMMAND:
1230						$opCommandsToDeleteByOpId[] = $operationDb['operationid'];
1231						$opCommandHstsToDeleteByOpId[] = $operationDb['operationid'];
1232						$opCommandGrpsToDeleteByOpId[] = $operationDb['operationid'];
1233						break;
1234
1235					case OPERATION_TYPE_GROUP_ADD:
1236						if ($operation['operationtype'] == OPERATION_TYPE_GROUP_REMOVE) {
1237							break;
1238						}
1239					case OPERATION_TYPE_GROUP_REMOVE:
1240						if ($operation['operationtype'] == OPERATION_TYPE_GROUP_ADD) {
1241							break;
1242						}
1243						$opGroupsToDeleteByOpId[] = $operationDb['operationid'];
1244						break;
1245
1246					case OPERATION_TYPE_TEMPLATE_ADD:
1247						if ($operation['operationtype'] == OPERATION_TYPE_TEMPLATE_REMOVE) {
1248							break;
1249						}
1250					case OPERATION_TYPE_TEMPLATE_REMOVE:
1251						if ($operation['operationtype'] == OPERATION_TYPE_TEMPLATE_ADD) {
1252							break;
1253						}
1254						$opTemplatesToDeleteByOpId[] = $operationDb['operationid'];
1255						break;
1256
1257					case OPERATION_TYPE_HOST_INVENTORY:
1258						$opInventoryToDeleteByOpId[] = $operationDb['operationid'];
1259						break;
1260
1261					case OPERATION_TYPE_ACK_MESSAGE:
1262						// falls through
1263					case OPERATION_TYPE_RECOVERY_MESSAGE:
1264						$opMessagesToDeleteByOpId[] = $operationDb['operationid'];
1265						break;
1266				}
1267			}
1268
1269			if (!isset($operation['operationtype'])) {
1270				$operation['operationtype'] = $operationDb['operationtype'];
1271			}
1272
1273			switch ($operation['operationtype']) {
1274				case OPERATION_TYPE_MESSAGE:
1275					if (!isset($operation['opmessage_grp'])) {
1276						$operation['opmessage_grp'] = [];
1277					}
1278					else {
1279						zbx_array_push($operation['opmessage_grp'], ['operationid' => $operation['operationid']]);
1280					}
1281
1282					if (!isset($operation['opmessage_usr'])) {
1283						$operation['opmessage_usr'] = [];
1284					}
1285					else {
1286						zbx_array_push($operation['opmessage_usr'], ['operationid' => $operation['operationid']]);
1287					}
1288
1289					if (!isset($operationDb['opmessage_usr'])) {
1290						$operationDb['opmessage_usr'] = [];
1291					}
1292					if (!isset($operationDb['opmessage_grp'])) {
1293						$operationDb['opmessage_grp'] = [];
1294					}
1295
1296					if ($type_changed) {
1297						$operation['opmessage']['operationid'] = $operation['operationid'];
1298						$opMessagesToInsert[] = $operation['opmessage'];
1299
1300						$opMessageGrpsToInsert = array_merge($opMessageGrpsToInsert, $operation['opmessage_grp']);
1301						$opMessageUsrsToInsert = array_merge($opMessageUsrsToInsert, $operation['opmessage_usr']);
1302					}
1303					else {
1304						if (array_key_exists('opmessage', $operation)) {
1305							$opMessagesToUpdate[] = [
1306								'values' => $operation['opmessage'],
1307								'where' => ['operationid' => $operation['operationid']]
1308							];
1309						}
1310
1311						$diff = zbx_array_diff($operation['opmessage_grp'], $operationDb['opmessage_grp'], 'usrgrpid');
1312						$opMessageGrpsToInsert = array_merge($opMessageGrpsToInsert, $diff['first']);
1313
1314						foreach ($diff['second'] as $opMessageGrp) {
1315							DB::delete('opmessage_grp', [
1316								'usrgrpid' => $opMessageGrp['usrgrpid'],
1317								'operationid' => $operation['operationid']
1318							]);
1319						}
1320
1321						$diff = zbx_array_diff($operation['opmessage_usr'], $operationDb['opmessage_usr'], 'userid');
1322						$opMessageUsrsToInsert = array_merge($opMessageUsrsToInsert, $diff['first']);
1323						foreach ($diff['second'] as $opMessageUsr) {
1324							DB::delete('opmessage_usr', [
1325								'userid' => $opMessageUsr['userid'],
1326								'operationid' => $operation['operationid']
1327							]);
1328						}
1329					}
1330					break;
1331
1332				case OPERATION_TYPE_COMMAND:
1333					if (!isset($operation['opcommand_grp'])) {
1334						$operation['opcommand_grp'] = [];
1335					}
1336					else {
1337						zbx_array_push($operation['opcommand_grp'], ['operationid' => $operation['operationid']]);
1338					}
1339
1340					if (!isset($operation['opcommand_hst'])) {
1341						$operation['opcommand_hst'] = [];
1342					}
1343					else {
1344						zbx_array_push($operation['opcommand_hst'], ['operationid' => $operation['operationid']]);
1345					}
1346
1347					if (!isset($operationDb['opcommand_grp'])) {
1348						$operationDb['opcommand_grp'] = [];
1349					}
1350					if (!isset($operationDb['opcommand_hst'])) {
1351						$operationDb['opcommand_hst'] = [];
1352					}
1353
1354					if ($type_changed) {
1355						$operation['opcommand']['operationid'] = $operation['operationid'];
1356						$opCommandsToInsert[] = $operation['opcommand'];
1357
1358						$opCommandGrpsToInsert = array_merge($opCommandGrpsToInsert, $operation['opcommand_grp']);
1359						$opCommandHstsToInsert = array_merge($opCommandHstsToInsert, $operation['opcommand_hst']);
1360					}
1361					else {
1362						// Check if "scriptid" needs to be updated in "opcommand" table.
1363						if (bccomp($operation['opcommand']['scriptid'], $operationDb['opcommand']['scriptid']) != 0) {
1364							$opCommandsToUpdate[] = [
1365								'values' => $operation['opcommand'],
1366								'where' => ['operationid' => $operation['operationid']]
1367							];
1368						}
1369
1370						$diff = zbx_array_diff($operation['opcommand_grp'], $operationDb['opcommand_grp'], 'groupid');
1371						$opCommandGrpsToInsert = array_merge($opCommandGrpsToInsert, $diff['first']);
1372
1373						foreach ($diff['second'] as $opMessageGrp) {
1374							DB::delete('opcommand_grp', [
1375								'groupid' => $opMessageGrp['groupid'],
1376								'operationid' => $operation['operationid']
1377							]);
1378						}
1379
1380						$diff = zbx_array_diff($operation['opcommand_hst'], $operationDb['opcommand_hst'], 'hostid');
1381						$opCommandHstsToInsert = array_merge($opCommandHstsToInsert, $diff['first']);
1382						$opCommandHostIds = zbx_objectValues($diff['second'], 'opcommand_hstid');
1383						if ($opCommandHostIds) {
1384							DB::delete('opcommand_hst', [
1385								'opcommand_hstid' => $opCommandHostIds
1386							]);
1387						}
1388					}
1389					break;
1390
1391				case OPERATION_TYPE_GROUP_ADD:
1392				case OPERATION_TYPE_GROUP_REMOVE:
1393					if (!isset($operation['opgroup'])) {
1394						$operation['opgroup'] = [];
1395					}
1396					else {
1397						zbx_array_push($operation['opgroup'], ['operationid' => $operation['operationid']]);
1398					}
1399
1400					if (!isset($operationDb['opgroup'])) {
1401						$operationDb['opgroup'] = [];
1402					}
1403
1404					$diff = zbx_array_diff($operation['opgroup'], $operationDb['opgroup'], 'groupid');
1405					$opGroupsToInsert = array_merge($opGroupsToInsert, $diff['first']);
1406					foreach ($diff['second'] as $opGroup) {
1407						DB::delete('opgroup', [
1408							'groupid' => $opGroup['groupid'],
1409							'operationid' => $operation['operationid']
1410						]);
1411					}
1412					break;
1413
1414				case OPERATION_TYPE_TEMPLATE_ADD:
1415				case OPERATION_TYPE_TEMPLATE_REMOVE:
1416					if (!isset($operation['optemplate'])) {
1417						$operation['optemplate'] = [];
1418					}
1419					else {
1420						zbx_array_push($operation['optemplate'], ['operationid' => $operation['operationid']]);
1421					}
1422
1423					if (!isset($operationDb['optemplate'])) {
1424						$operationDb['optemplate'] = [];
1425					}
1426
1427					$diff = zbx_array_diff($operation['optemplate'], $operationDb['optemplate'], 'templateid');
1428					$opTemplateToInsert = array_merge($opTemplateToInsert, $diff['first']);
1429
1430					foreach ($diff['second'] as $opTemplate) {
1431						DB::delete('optemplate', [
1432							'templateid' => $opTemplate['templateid'],
1433							'operationid' => $operation['operationid']
1434						]);
1435					}
1436					break;
1437
1438				case OPERATION_TYPE_HOST_INVENTORY:
1439					if ($type_changed) {
1440						$operation['opinventory']['operationid'] = $operation['operationid'];
1441						$opInventoryToInsert[] = $operation['opinventory'];
1442					}
1443					else {
1444						$opInventoryToUpdate[] = [
1445							'values' => $operation['opinventory'],
1446							'where' => ['operationid' => $operation['operationid']]
1447						];
1448					}
1449					break;
1450
1451				case OPERATION_TYPE_ACK_MESSAGE:
1452					// falls through
1453				case OPERATION_TYPE_RECOVERY_MESSAGE:
1454					if ($type_changed) {
1455						$operation['opmessage']['operationid'] = $operation['operationid'];
1456						$opMessagesToInsert[] = $operation['opmessage'];
1457					}
1458					elseif (array_key_exists('opmessage', $operation)) {
1459						$opMessagesToUpdate[] = [
1460							'values' => $operation['opmessage'],
1461							'where' => ['operationid' => $operation['operationid']]
1462						];
1463					}
1464					break;
1465			}
1466
1467			if (!isset($operation['opconditions'])) {
1468				$operation['opconditions'] = [];
1469			}
1470			else {
1471				zbx_array_push($operation['opconditions'], ['operationid' => $operation['operationid']]);
1472			}
1473
1474			self::validateOperationConditions($operation['opconditions']);
1475
1476			$db_opconditions = array_key_exists('opconditions', $operationDb) ? $operationDb['opconditions'] : [];
1477			$diff = zbx_array_diff($operation['opconditions'], $db_opconditions, 'opconditionid');
1478			$opConditionsToInsert = array_merge($opConditionsToInsert, $diff['first']);
1479
1480			$opConditionsIdsToDelete = zbx_objectValues($diff['second'], 'opconditionid');
1481			if (!empty($opConditionsIdsToDelete)) {
1482				DB::delete('opconditions', ['opconditionid' => $opConditionsIdsToDelete]);
1483			}
1484
1485			$operationId = $operation['operationid'];
1486			unset($operation['operationid']);
1487			if (!empty($operation)) {
1488				$operationsUpdate[] = [
1489					'values' => $operation,
1490					'where' => ['operationid' => $operationId]
1491				];
1492			}
1493		}
1494
1495		DB::update('operations', $operationsUpdate);
1496
1497		if (!empty($opMessagesToDeleteByOpId)) {
1498			DB::delete('opmessage', ['operationid' => $opMessagesToDeleteByOpId]);
1499		}
1500		if (!empty($opCommandsToDeleteByOpId)) {
1501			DB::delete('opcommand', ['operationid' => $opCommandsToDeleteByOpId]);
1502		}
1503		if (!empty($opMessageGrpsToDeleteByOpId)) {
1504			DB::delete('opmessage_grp', ['operationid' => $opMessageGrpsToDeleteByOpId]);
1505		}
1506		if (!empty($opMessageUsrsToDeleteByOpId)) {
1507			DB::delete('opmessage_usr', ['operationid' => $opMessageUsrsToDeleteByOpId]);
1508		}
1509		if (!empty($opCommandHstsToDeleteByOpId)) {
1510			DB::delete('opcommand_hst', ['operationid' => $opCommandHstsToDeleteByOpId]);
1511		}
1512		if (!empty($opCommandGrpsToDeleteByOpId)) {
1513			DB::delete('opcommand_grp', ['operationid' => $opCommandGrpsToDeleteByOpId]);
1514		}
1515		if (!empty($opCommandGrpsToDeleteByOpId)) {
1516			DB::delete('opcommand_grp', ['opcommand_grpid' => $opCommandGrpsToDeleteByOpId]);
1517		}
1518		if (!empty($opCommandHstsToDeleteByOpId)) {
1519			DB::delete('opcommand_hst', ['opcommand_hstid' => $opCommandHstsToDeleteByOpId]);
1520		}
1521		if (!empty($opGroupsToDeleteByOpId)) {
1522			DB::delete('opgroup', ['operationid' => $opGroupsToDeleteByOpId]);
1523		}
1524		if (!empty($opTemplatesToDeleteByOpId)) {
1525			DB::delete('optemplate', ['operationid' => $opTemplatesToDeleteByOpId]);
1526		}
1527		if (!empty($opInventoryToDeleteByOpId)) {
1528			DB::delete('opinventory', ['operationid' => $opInventoryToDeleteByOpId]);
1529		}
1530
1531		DB::insert('opmessage', $opMessagesToInsert, false);
1532		DB::insert('opcommand', $opCommandsToInsert, false);
1533		DB::insert('opmessage_grp', $opMessageGrpsToInsert);
1534		DB::insert('opmessage_usr', $opMessageUsrsToInsert);
1535		DB::insert('opcommand_grp', $opCommandGrpsToInsert);
1536		DB::insert('opcommand_hst', $opCommandHstsToInsert);
1537		DB::insert('opgroup', $opGroupsToInsert);
1538		DB::insert('optemplate', $opTemplateToInsert);
1539		DB::update('opmessage', $opMessagesToUpdate);
1540		DB::update('opcommand', $opCommandsToUpdate);
1541		DB::insert('opconditions', $opConditionsToInsert);
1542		DB::insert('opinventory', $opInventoryToInsert, false);
1543		DB::update('opinventory', $opInventoryToUpdate);
1544	}
1545
1546	protected function deleteOperations($operationIds) {
1547		DB::delete('operations', ['operationid' => $operationIds]);
1548	}
1549
1550	/**
1551	 * @param array $actionids
1552	 *
1553	 * @return array
1554	 */
1555	public function delete(array $actionids) {
1556		$api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true];
1557		if (!CApiInputValidator::validate($api_input_rules, $actionids, '/', $error)) {
1558			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
1559		}
1560
1561		$db_actions = $this->get([
1562			'output' => ['actionid', 'name'],
1563			'actionids' => $actionids,
1564			'editable' => true,
1565			'preservekeys' => true
1566		]);
1567
1568		foreach ($actionids as $actionid) {
1569			if (!array_key_exists($actionid, $db_actions)) {
1570				self::exception(ZBX_API_ERROR_PERMISSIONS,
1571					_('No permissions to referred object or it does not exist!')
1572				);
1573			}
1574		}
1575
1576		DB::delete('actions', ['actionid' => $actionids]);
1577
1578		$this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_ACTION, $db_actions);
1579
1580		return ['actionids' => $actionids];
1581	}
1582
1583	/**
1584	 * Validate condition type and operator based on action event source.
1585	 *
1586	 * @param string $name                           Action name.
1587	 * @param int    $eventsource                    Action event source.
1588	 * @param array  $conditions                     Conditions data array.
1589	 * @param string $conditions[]['conditiontype']  Action condition type.
1590	 * @param int    $conditions[]['operator']       Action condition operator.
1591	 *
1592	 * @return bool
1593	 */
1594	public function validateFilterConditionsIntegrity($name, $eventsource, array $conditions) {
1595		foreach ($conditions as $condition) {
1596			if (!in_array($condition['conditiontype'], $this->valid_condition_types[$eventsource])) {
1597				self::exception(ZBX_API_ERROR_PARAMETERS,
1598					_s('Incorrect filter condition type for action "%1$s".', $name)
1599				);
1600			}
1601
1602			if (!in_array($condition['operator'],
1603					$this->valid_condition_type_operators[$condition['conditiontype']])) {
1604				self::exception(ZBX_API_ERROR_PARAMETERS,
1605					_s('Incorrect filter condition operator for action "%1$s".', $name)
1606				);
1607			}
1608		}
1609	}
1610
1611	/**
1612	 * Validate operation, recovery operation, acknowledge operations.
1613	 *
1614	 * @param array $operations  Operation data array.
1615	 *
1616	 * @return bool
1617	 */
1618	public function validateOperationsIntegrity(array $operations) {
1619		$operations = zbx_toArray($operations);
1620
1621		$all_groupids = [];
1622		$all_hostids = [];
1623		$all_templateids = [];
1624		$all_userids = [];
1625		$all_usrgrpids = [];
1626		$all_mediatypeids = [];
1627
1628		$valid_operationtypes = [
1629			ACTION_OPERATION => [
1630				EVENT_SOURCE_TRIGGERS => [OPERATION_TYPE_MESSAGE, OPERATION_TYPE_COMMAND],
1631				EVENT_SOURCE_DISCOVERY => [
1632					OPERATION_TYPE_MESSAGE, OPERATION_TYPE_COMMAND, OPERATION_TYPE_GROUP_ADD,
1633					OPERATION_TYPE_GROUP_REMOVE, OPERATION_TYPE_TEMPLATE_ADD, OPERATION_TYPE_TEMPLATE_REMOVE,
1634					OPERATION_TYPE_HOST_ADD, OPERATION_TYPE_HOST_REMOVE, OPERATION_TYPE_HOST_ENABLE,
1635					OPERATION_TYPE_HOST_DISABLE, OPERATION_TYPE_HOST_INVENTORY
1636				],
1637				EVENT_SOURCE_AUTOREGISTRATION => [
1638					OPERATION_TYPE_MESSAGE, OPERATION_TYPE_COMMAND, OPERATION_TYPE_GROUP_ADD,
1639					OPERATION_TYPE_GROUP_REMOVE, OPERATION_TYPE_TEMPLATE_ADD, OPERATION_TYPE_TEMPLATE_REMOVE,
1640					OPERATION_TYPE_HOST_ADD, OPERATION_TYPE_HOST_REMOVE, OPERATION_TYPE_HOST_ENABLE,
1641					OPERATION_TYPE_HOST_DISABLE, OPERATION_TYPE_HOST_INVENTORY
1642				],
1643				EVENT_SOURCE_INTERNAL => [OPERATION_TYPE_MESSAGE]
1644			],
1645			ACTION_RECOVERY_OPERATION => [
1646				EVENT_SOURCE_TRIGGERS => [OPERATION_TYPE_MESSAGE, OPERATION_TYPE_COMMAND,
1647					OPERATION_TYPE_RECOVERY_MESSAGE
1648				],
1649				EVENT_SOURCE_INTERNAL => [OPERATION_TYPE_MESSAGE, OPERATION_TYPE_RECOVERY_MESSAGE]
1650			],
1651			ACTION_ACKNOWLEDGE_OPERATION => [
1652				EVENT_SOURCE_TRIGGERS => [OPERATION_TYPE_MESSAGE, OPERATION_TYPE_COMMAND, OPERATION_TYPE_ACK_MESSAGE]
1653			]
1654		];
1655
1656		$required_fields = ['eventsource', 'recovery', 'operationtype'];
1657
1658		$default_msg_validator = new CLimitedSetValidator([
1659			'values' => [0, 1]
1660		]);
1661
1662		foreach ($operations as $operation) {
1663			foreach ($required_fields as $field) {
1664				if (!array_key_exists($field, $operation)) {
1665					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Field "%1$s" is mandatory.', $field));
1666				}
1667			}
1668			$eventsource = $operation['eventsource'];
1669			$recovery = $operation['recovery'];
1670			$operationtype = $operation['operationtype'];
1671
1672			if ($recovery == ACTION_OPERATION) {
1673				if ((array_key_exists('esc_step_from', $operation) || array_key_exists('esc_step_to', $operation))
1674						&& (!array_key_exists('esc_step_from', $operation)
1675							|| !array_key_exists('esc_step_to', $operation))) {
1676					self::exception(ZBX_API_ERROR_PARAMETERS, _('esc_step_from and esc_step_to must be set together.'));
1677				}
1678
1679				if (array_key_exists('esc_step_from', $operation) && array_key_exists('esc_step_to', $operation)) {
1680					if ($operation['esc_step_from'] < 1 || $operation['esc_step_to'] < 0) {
1681						self::exception(ZBX_API_ERROR_PARAMETERS,
1682							_('Incorrect action operation escalation step values.')
1683						);
1684					}
1685
1686					if ($operation['esc_step_from'] > $operation['esc_step_to'] && $operation['esc_step_to'] != 0) {
1687						self::exception(ZBX_API_ERROR_PARAMETERS,
1688							_('Incorrect action operation escalation step values.')
1689						);
1690					}
1691				}
1692
1693				if (array_key_exists('esc_period', $operation)
1694						&& !validateTimeUnit($operation['esc_period'], SEC_PER_MIN, SEC_PER_WEEK, true, $error,
1695							['usermacros' => true])) {
1696					self::exception(ZBX_API_ERROR_PARAMETERS,
1697						_s('Incorrect value for field "%1$s": %2$s.', 'esc_period', $error)
1698					);
1699				}
1700			}
1701
1702			if (!array_key_exists($eventsource, $valid_operationtypes[$recovery])
1703					|| !in_array($operationtype, $valid_operationtypes[$recovery][$eventsource])) {
1704				self::exception(ZBX_API_ERROR_PARAMETERS,
1705					_s('Incorrect action operation type "%1$s" for event source "%2$s".', $operationtype, $eventsource)
1706				);
1707			}
1708
1709			switch ($operationtype) {
1710				case OPERATION_TYPE_MESSAGE:
1711					$userids = array_key_exists('opmessage_usr', $operation)
1712						? zbx_objectValues($operation['opmessage_usr'], 'userid')
1713						: [];
1714
1715					$usrgrpids = array_key_exists('opmessage_grp', $operation)
1716						? zbx_objectValues($operation['opmessage_grp'], 'usrgrpid')
1717						: [];
1718
1719					if (!$userids && !$usrgrpids) {
1720						self::exception(ZBX_API_ERROR_PARAMETERS, _('No recipients for action operation message.'));
1721					}
1722
1723					$all_userids = array_merge($all_userids, $userids);
1724					$all_usrgrpids = array_merge($all_usrgrpids, $usrgrpids);
1725					// falls through
1726				case OPERATION_TYPE_ACK_MESSAGE:
1727					$message = array_key_exists('opmessage', $operation) ? $operation['opmessage'] : [];
1728
1729					if (array_key_exists('mediatypeid', $message) && $message['mediatypeid']) {
1730						$all_mediatypeids[$message['mediatypeid']] = true;
1731					}
1732
1733					if (array_key_exists('default_msg', $message)
1734							&& (!$default_msg_validator->validate($message['default_msg']))) {
1735						self::exception(ZBX_API_ERROR_PARAMETERS,
1736							_s('Incorrect value "%1$s" for "%2$s" field: must be between %3$s and %4$s.',
1737							$message['default_msg'], 'default_msg', 0, 1
1738						));
1739					}
1740					break;
1741				case OPERATION_TYPE_COMMAND:
1742					if (!array_key_exists('scriptid', $operation['opcommand'])
1743							|| $operation['opcommand']['scriptid'] === null) {
1744						self::exception(ZBX_API_ERROR_PARAMETERS,
1745							_('No script specified for action operation command.')
1746						);
1747					}
1748
1749					$scripts = API::Script()->get([
1750						'output' => ['scriptid', 'name'],
1751						'scriptids' => $operation['opcommand']['scriptid'],
1752						'filter' => ['scope' => ZBX_SCRIPT_SCOPE_ACTION],
1753						'preservekeys' => true
1754					]);
1755
1756					if (!array_key_exists($operation['opcommand']['scriptid'], $scripts)) {
1757						self::exception(ZBX_API_ERROR_PARAMETERS, _(
1758							'Specified script does not exist or you do not have rights on it for action operation command.'
1759						));
1760					}
1761
1762					$groupids = [];
1763					if (array_key_exists('opcommand_grp', $operation)) {
1764						$groupids = zbx_objectValues($operation['opcommand_grp'], 'groupid');
1765					}
1766
1767					$hostids = [];
1768					$withoutCurrent = true;
1769					if (array_key_exists('opcommand_hst', $operation)) {
1770						foreach ($operation['opcommand_hst'] as $hstCommand) {
1771							if (!is_array($hstCommand) || !array_key_exists('hostid', $hstCommand)) {
1772								self::exception(ZBX_API_ERROR_PARAMETERS,
1773									_s('Incorrect value for field "%1$s": %2$s.', 'hostid', _('cannot be empty'))
1774								);
1775							}
1776
1777							if ($hstCommand['hostid'] == 0) {
1778								$withoutCurrent = false;
1779							}
1780							else {
1781								$hostids[$hstCommand['hostid']] = $hstCommand['hostid'];
1782							}
1783						}
1784					}
1785
1786					if (!$groupids && !$hostids && $withoutCurrent) {
1787						self::exception(ZBX_API_ERROR_PARAMETERS,
1788							_s('You did not specify targets for action operation global script "%1$s".',
1789								$scripts[$operation['opcommand']['scriptid']]['name']
1790							)
1791						);
1792					}
1793
1794					$all_hostids = array_merge($all_hostids, $hostids);
1795					$all_groupids = array_merge($all_groupids, $groupids);
1796					break;
1797				case OPERATION_TYPE_GROUP_ADD:
1798				case OPERATION_TYPE_GROUP_REMOVE:
1799					$groupids = array_key_exists('opgroup', $operation)
1800						? zbx_objectValues($operation['opgroup'], 'groupid')
1801						: [];
1802
1803					if (!$groupids) {
1804						self::exception(ZBX_API_ERROR_PARAMETERS, _('Operation has no group to operate.'));
1805					}
1806
1807					$all_groupids = array_merge($all_groupids, $groupids);
1808					break;
1809				case OPERATION_TYPE_TEMPLATE_ADD:
1810				case OPERATION_TYPE_TEMPLATE_REMOVE:
1811					$templateids = isset($operation['optemplate'])
1812						? zbx_objectValues($operation['optemplate'], 'templateid')
1813						: [];
1814
1815					if (!$templateids) {
1816						self::exception(ZBX_API_ERROR_PARAMETERS, _('Operation has no template to operate.'));
1817					}
1818
1819					$all_templateids = array_merge($all_templateids, $templateids);
1820					break;
1821				case OPERATION_TYPE_HOST_ADD:
1822				case OPERATION_TYPE_HOST_REMOVE:
1823				case OPERATION_TYPE_HOST_ENABLE:
1824				case OPERATION_TYPE_HOST_DISABLE:
1825					break;
1826
1827				case OPERATION_TYPE_HOST_INVENTORY:
1828					if (!array_key_exists('opinventory', $operation)
1829							|| !array_key_exists('inventory_mode', $operation['opinventory'])) {
1830						self::exception(ZBX_API_ERROR_PARAMETERS,
1831							_('No inventory mode specified for action operation.')
1832						);
1833					}
1834					if ($operation['opinventory']['inventory_mode'] != HOST_INVENTORY_MANUAL
1835							&& $operation['opinventory']['inventory_mode'] != HOST_INVENTORY_AUTOMATIC) {
1836						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect inventory mode in action operation.'));
1837					}
1838					break;
1839			}
1840		}
1841
1842		$this->checkMediatypesExists($all_mediatypeids, _s('Incorrect value for field "%1$s": %2$s.', 'mediatypeid',
1843			_('No permissions to referred object or it does not exist!')
1844		));
1845		$this->checkHostGroupsPermissions($all_groupids, _(
1846			'Incorrect action operation host group. Host group does not exist or you have no access to this host group.'
1847		));
1848		$this->checkHostsPermissions($all_hostids,
1849			_('Incorrect action operation host. Host does not exist or you have no access to this host.')
1850		);
1851		$this->checkTemplatesPermissions($all_templateids,
1852			_('Incorrect action operation template. Template does not exist or you have no access to this template.')
1853		);
1854		$this->checkUsersPermissions($all_userids,
1855			_('Incorrect action operation user. User does not exist or you have no access to this user.')
1856		);
1857		$this->checkUserGroupsPermissions($all_usrgrpids, _(
1858			'Incorrect action operation user group. User group does not exist or you have no access to this user group.'
1859		));
1860
1861		return true;
1862	}
1863
1864	/**
1865	 * Validate operation conditions.
1866	 *
1867	 * @static
1868	 * @param $conditions
1869	 * @return bool
1870	 */
1871	public static function validateOperationConditions($conditions) {
1872		$conditions = zbx_toArray($conditions);
1873		$ackStatuses = [
1874			EVENT_ACKNOWLEDGED => 1,
1875			EVENT_NOT_ACKNOWLEDGED => 1
1876		];
1877
1878		foreach ($conditions as $condition) {
1879			switch ($condition['conditiontype']) {
1880				case CONDITION_TYPE_EVENT_ACKNOWLEDGED:
1881					if (!isset($ackStatuses[$condition['value']])) {
1882						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation condition acknowledge type.'));
1883					}
1884					break;
1885
1886				default:
1887					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation condition type.'));
1888					break;
1889			}
1890		}
1891
1892		return true;
1893	}
1894
1895	protected function addRelatedObjects(array $options, array $result) {
1896		$result = parent::addRelatedObjects($options, $result);
1897
1898		$actionIds = array_keys($result);
1899
1900		// adding formulas
1901		if ($options['selectFilter'] !== null) {
1902			$formulaRequested = $this->outputIsRequested('formula', $options['selectFilter']);
1903			$evalFormulaRequested = $this->outputIsRequested('eval_formula', $options['selectFilter']);
1904			$conditionsRequested = $this->outputIsRequested('conditions', $options['selectFilter']);
1905
1906			$filters = [];
1907			foreach ($result as $action) {
1908				$filters[$action['actionid']] = [
1909					'evaltype' => $action['evaltype'],
1910					'formula' => isset($action['formula']) ? $action['formula'] : ''
1911				];
1912			}
1913
1914			if ($formulaRequested || $evalFormulaRequested || $conditionsRequested) {
1915				$conditions = API::getApiService()->select('conditions', [
1916						'output' => ['actionid', 'conditionid', 'conditiontype', 'operator', 'value', 'value2'],
1917						'filter' => ['actionid' => $actionIds],
1918					'preservekeys' => true
1919				]);
1920
1921				$relationMap = $this->createRelationMap($conditions, 'actionid', 'conditionid');
1922				$filters = $relationMap->mapMany($filters, $conditions, 'conditions');
1923
1924				foreach ($filters as &$filter) {
1925					// in case of a custom expression - use the given formula
1926					if ($filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
1927						$formula = $filter['formula'];
1928					}
1929					// in other cases - generate the formula automatically
1930					else {
1931						$conditions = $filter['conditions'];
1932
1933						// sort conditions
1934						$sortFields = [
1935							['field' => 'conditiontype', 'order' => ZBX_SORT_DOWN],
1936							['field' => 'operator', 'order' => ZBX_SORT_DOWN],
1937							['field' => 'value2', 'order' => ZBX_SORT_DOWN],
1938							['field' => 'value', 'order' => ZBX_SORT_DOWN]
1939						];
1940						CArrayHelper::sort($conditions, $sortFields);
1941
1942						$conditionsForFormula = [];
1943						foreach ($conditions as $condition) {
1944							$conditionsForFormula[$condition['conditionid']] = $condition['conditiontype'];
1945						}
1946						$formula = CConditionHelper::getFormula($conditionsForFormula, $filter['evaltype']);
1947					}
1948
1949					// generate formulaids from the effective formula
1950					$formulaIds = CConditionHelper::getFormulaIds($formula);
1951					foreach ($filter['conditions'] as &$condition) {
1952						$condition['formulaid'] = $formulaIds[$condition['conditionid']];
1953					}
1954					unset($condition);
1955
1956					// generated a letter based formula only for actions with custom expressions
1957					if ($formulaRequested && $filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
1958						$filter['formula'] = CConditionHelper::replaceNumericIds($formula, $formulaIds);
1959					}
1960
1961					if ($evalFormulaRequested) {
1962						$filter['eval_formula'] = CConditionHelper::replaceNumericIds($formula, $formulaIds);
1963					}
1964				}
1965				unset($filter);
1966			}
1967
1968			// add filters to the result
1969			foreach ($result as &$action) {
1970				$action['filter'] = $filters[$action['actionid']];
1971			}
1972			unset($action);
1973		}
1974
1975		// Acknowledge operations data.
1976		if ($options['selectAcknowledgeOperations'] !== null
1977				&& $options['selectAcknowledgeOperations'] != API_OUTPUT_COUNT) {
1978			$ack_operations = API::getApiService()->select('operations', [
1979				'output' => $this->outputExtend($options['selectAcknowledgeOperations'],
1980					['operationid', 'actionid', 'operationtype']
1981				),
1982				'filter' => ['actionid' => $actionIds, 'recovery' => ACTION_ACKNOWLEDGE_OPERATION],
1983				'preservekeys' => true
1984			]);
1985
1986			foreach ($result as &$action) {
1987				$action['acknowledgeOperations'] = [];
1988			}
1989			unset($action);
1990
1991			$ack_operations = $this->getAcknowledgeOperations($ack_operations, $options['selectAcknowledgeOperations']);
1992
1993			foreach ($ack_operations as $ack_operation) {
1994				$actionid = $ack_operation['actionid'];
1995				unset($ack_operation['actionid'], $ack_operation['recovery']);
1996				$result[$actionid]['acknowledgeOperations'][] = $ack_operation;
1997			}
1998		}
1999
2000		// adding operations
2001		if ($options['selectOperations'] !== null && $options['selectOperations'] != API_OUTPUT_COUNT) {
2002			$operations = API::getApiService()->select('operations', [
2003				'output' => $this->outputExtend($options['selectOperations'],
2004					['operationid', 'actionid', 'operationtype']
2005				),
2006				'filter' => ['actionid' => $actionIds, 'recovery' => ACTION_OPERATION],
2007				'preservekeys' => true
2008			]);
2009			$relationMap = $this->createRelationMap($operations, 'actionid', 'operationid');
2010			$operationIds = $relationMap->getRelatedIds();
2011
2012			if ($this->outputIsRequested('opconditions', $options['selectOperations'])) {
2013				foreach ($operations as &$operation) {
2014					$operation['opconditions'] = [];
2015				}
2016				unset($operation);
2017
2018				$res = DBselect('SELECT op.* FROM opconditions op WHERE '.dbConditionInt('op.operationid', $operationIds));
2019				while ($opcondition = DBfetch($res)) {
2020					$operations[$opcondition['operationid']]['opconditions'][] = $opcondition;
2021				}
2022			}
2023
2024			$opmessage = [];
2025			$opcommand = [];
2026			$opgroup = [];
2027			$optemplate = [];
2028			$opinventory = [];
2029
2030			foreach ($operations as $operationid => $operation) {
2031				unset($operations[$operationid]['recovery']);
2032
2033				switch ($operation['operationtype']) {
2034					case OPERATION_TYPE_MESSAGE:
2035						$opmessage[] = $operationid;
2036						break;
2037					case OPERATION_TYPE_COMMAND:
2038						$opcommand[] = $operationid;
2039						break;
2040					case OPERATION_TYPE_GROUP_ADD:
2041					case OPERATION_TYPE_GROUP_REMOVE:
2042						$opgroup[] = $operationid;
2043						break;
2044					case OPERATION_TYPE_TEMPLATE_ADD:
2045					case OPERATION_TYPE_TEMPLATE_REMOVE:
2046						$optemplate[] = $operationid;
2047						break;
2048					case OPERATION_TYPE_HOST_ADD:
2049					case OPERATION_TYPE_HOST_REMOVE:
2050					case OPERATION_TYPE_HOST_ENABLE:
2051					case OPERATION_TYPE_HOST_DISABLE:
2052						break;
2053					case OPERATION_TYPE_HOST_INVENTORY:
2054						$opinventory[] = $operationid;
2055						break;
2056				}
2057			}
2058
2059			// get OPERATION_TYPE_MESSAGE data
2060			if ($opmessage) {
2061				if ($this->outputIsRequested('opmessage', $options['selectOperations'])) {
2062					foreach ($opmessage as $operationId) {
2063						$operations[$operationId]['opmessage'] = [];
2064					}
2065
2066					$db_opmessages = DBselect(
2067						'SELECT o.operationid,o.default_msg,o.subject,o.message,o.mediatypeid'.
2068						' FROM opmessage o'.
2069						' WHERE '.dbConditionInt('o.operationid', $opmessage)
2070					);
2071					while ($db_opmessage = DBfetch($db_opmessages)) {
2072						$operationid = $db_opmessage['operationid'];
2073						unset($db_opmessage['operationid']);
2074						$operations[$operationid]['opmessage'] = $db_opmessage;
2075					}
2076				}
2077
2078				if ($this->outputIsRequested('opmessage_grp', $options['selectOperations'])) {
2079					foreach ($opmessage as $operationId) {
2080						$operations[$operationId]['opmessage_grp'] = [];
2081					}
2082
2083					$db_opmessages_grp = DBselect(
2084						'SELECT og.operationid,og.usrgrpid'.
2085						' FROM opmessage_grp og'.
2086						' WHERE '.dbConditionInt('og.operationid', $opmessage)
2087					);
2088					while ($db_opmessage_grp = DBfetch($db_opmessages_grp)) {
2089						$operationid = $db_opmessage_grp['operationid'];
2090						unset($db_opmessage_grp['operationid']);
2091						$operations[$operationid]['opmessage_grp'][] = $db_opmessage_grp;
2092					}
2093				}
2094
2095				if ($this->outputIsRequested('opmessage_usr', $options['selectOperations'])) {
2096					foreach ($opmessage as $operationId) {
2097						$operations[$operationId]['opmessage_usr'] = [];
2098					}
2099
2100					$db_opmessages_usr = DBselect(
2101						'SELECT ou.operationid,ou.userid'.
2102						' FROM opmessage_usr ou'.
2103						' WHERE '.dbConditionInt('ou.operationid', $opmessage)
2104					);
2105					while ($db_opmessage_usr = DBfetch($db_opmessages_usr)) {
2106						$operationid = $db_opmessage_usr['operationid'];
2107						unset($db_opmessage_usr['operationid']);
2108						$operations[$operationid]['opmessage_usr'][] = $db_opmessage_usr;
2109					}
2110				}
2111			}
2112
2113			// get OPERATION_TYPE_COMMAND data
2114			if ($opcommand) {
2115				if ($this->outputIsRequested('opcommand', $options['selectOperations'])) {
2116					foreach ($opcommand as $operationId) {
2117						$operations[$operationId]['opcommand'] = [];
2118					}
2119
2120					$db_opcommands = DBselect(
2121						'SELECT o.operationid,o.scriptid'.
2122						' FROM opcommand o'.
2123						' WHERE '.dbConditionInt('o.operationid', $opcommand)
2124					);
2125					while ($db_opcommand = DBfetch($db_opcommands)) {
2126						$operationid = $db_opcommand['operationid'];
2127						unset($db_opcommand['operationid']);
2128						$operations[$operationid]['opcommand'] = $db_opcommand;
2129					}
2130				}
2131
2132				if ($this->outputIsRequested('opcommand_hst', $options['selectOperations'])) {
2133					foreach ($opcommand as $operationId) {
2134						$operations[$operationId]['opcommand_hst'] = [];
2135					}
2136
2137					$db_opcommands_hst = DBselect(
2138						'SELECT oh.opcommand_hstid,oh.operationid,oh.hostid'.
2139						' FROM opcommand_hst oh'.
2140						' WHERE '.dbConditionInt('oh.operationid', $opcommand)
2141					);
2142					while ($db_opcommand_hst = DBfetch($db_opcommands_hst)) {
2143						$operationid = $db_opcommand_hst['operationid'];
2144						unset($db_opcommand_hst['operationid']);
2145						$operations[$operationid]['opcommand_hst'][] = $db_opcommand_hst;
2146					}
2147				}
2148
2149				if ($this->outputIsRequested('opcommand_grp', $options['selectOperations'])) {
2150					foreach ($opcommand as $operationId) {
2151						$operations[$operationId]['opcommand_grp'] = [];
2152					}
2153
2154					$db_opcommands_grp = DBselect(
2155						'SELECT og.opcommand_grpid,og.operationid,og.groupid'.
2156						' FROM opcommand_grp og'.
2157						' WHERE '.dbConditionInt('og.operationid', $opcommand)
2158					);
2159					while ($db_opcommand_grp = DBfetch($db_opcommands_grp)) {
2160						$operationid = $db_opcommand_grp['operationid'];
2161						unset($db_opcommand_grp['operationid']);
2162						$operations[$operationid]['opcommand_grp'][] = $db_opcommand_grp;
2163					}
2164				}
2165			}
2166
2167			// get OPERATION_TYPE_GROUP_ADD, OPERATION_TYPE_GROUP_REMOVE data
2168			if ($opgroup) {
2169				if ($this->outputIsRequested('opgroup', $options['selectOperations'])) {
2170					foreach ($opgroup as $operationId) {
2171						$operations[$operationId]['opgroup'] = [];
2172					}
2173
2174					$db_opgroups = DBselect(
2175						'SELECT o.operationid,o.groupid'.
2176						' FROM opgroup o'.
2177						' WHERE '.dbConditionInt('o.operationid', $opgroup)
2178					);
2179					while ($db_opgroup = DBfetch($db_opgroups)) {
2180						$operationid = $db_opgroup['operationid'];
2181						unset($db_opgroup['operationid']);
2182						$operations[$operationid]['opgroup'][] = $db_opgroup;
2183					}
2184				}
2185			}
2186
2187			// get OPERATION_TYPE_TEMPLATE_ADD, OPERATION_TYPE_TEMPLATE_REMOVE data
2188			if ($optemplate) {
2189				if ($this->outputIsRequested('optemplate', $options['selectOperations'])) {
2190					foreach ($optemplate as $operationId) {
2191						$operations[$operationId]['optemplate'] = [];
2192					}
2193
2194					$db_optemplates = DBselect(
2195						'SELECT o.operationid,o.templateid'.
2196						' FROM optemplate o'.
2197						' WHERE '.dbConditionInt('o.operationid', $optemplate)
2198					);
2199					while ($db_optemplate = DBfetch($db_optemplates)) {
2200						$operationid = $db_optemplate['operationid'];
2201						unset($db_optemplate['operationid']);
2202						$operations[$operationid]['optemplate'][] = $db_optemplate;
2203					}
2204				}
2205			}
2206
2207			// get OPERATION_TYPE_HOST_INVENTORY data
2208			if ($opinventory) {
2209				if ($this->outputIsRequested('opinventory', $options['selectOperations'])) {
2210					foreach ($opinventory as $operationId) {
2211						$operations[$operationId]['opinventory'] = [];
2212					}
2213
2214					$db_opinventories = DBselect(
2215						'SELECT o.operationid,o.inventory_mode'.
2216						' FROM opinventory o'.
2217						' WHERE '.dbConditionInt('o.operationid', $opinventory)
2218					);
2219					while ($db_opinventory = DBfetch($db_opinventories)) {
2220						$operationid = $db_opinventory['operationid'];
2221						unset($db_opinventory['operationid']);
2222						$operations[$operationid]['opinventory'] = $db_opinventory;
2223					}
2224				}
2225			}
2226
2227			$operations = $this->unsetExtraFields($operations, ['operationid', 'actionid', 'operationtype'],
2228				$options['selectOperations']
2229			);
2230			$result = $relationMap->mapMany($result, $operations, 'operations');
2231		}
2232
2233		// Adding recovery operations.
2234		if ($options['selectRecoveryOperations'] !== null
2235				&& $options['selectRecoveryOperations'] != API_OUTPUT_COUNT) {
2236			$recovery_operations = API::getApiService()->select('operations', [
2237				'output' => $this->outputExtend($options['selectRecoveryOperations'],
2238					['operationid', 'actionid', 'operationtype']
2239				),
2240				'filter' => ['actionid' => $actionIds, 'recovery' => ACTION_RECOVERY_OPERATION],
2241				'preservekeys' => true
2242			]);
2243
2244			$relationMap = $this->createRelationMap($recovery_operations, 'actionid', 'operationid');
2245			$recovery_operationids = $relationMap->getRelatedIds();
2246
2247			if ($this->outputIsRequested('opconditions', $options['selectRecoveryOperations'])) {
2248				foreach ($recovery_operations as &$recovery_operation) {
2249					unset($recovery_operation['esc_period'], $recovery_operation['esc_step_from'],
2250						$recovery_operation['esc_step_to']
2251					);
2252
2253					$recovery_operation['opconditions'] = [];
2254				}
2255				unset($recovery_operation);
2256
2257				$res = DBselect('SELECT op.* FROM opconditions op WHERE '.
2258					dbConditionInt('op.operationid', $recovery_operationids)
2259				);
2260				while ($opcondition = DBfetch($res)) {
2261					$recovery_operations[$opcondition['operationid']]['opconditions'][] = $opcondition;
2262				}
2263			}
2264
2265			$opmessage = [];
2266			$opcommand = [];
2267			$op_recovery_message = [];
2268
2269			foreach ($recovery_operations as $recovery_operationid => $recovery_operation) {
2270				unset($recovery_operations[$recovery_operationid]['recovery']);
2271
2272				switch ($recovery_operation['operationtype']) {
2273					case OPERATION_TYPE_MESSAGE:
2274						$opmessage[] = $recovery_operationid;
2275						break;
2276					case OPERATION_TYPE_COMMAND:
2277						$opcommand[] = $recovery_operationid;
2278						break;
2279					case OPERATION_TYPE_RECOVERY_MESSAGE:
2280						$op_recovery_message[] = $recovery_operationid;
2281						break;
2282				}
2283			}
2284
2285			// Get OPERATION_TYPE_MESSAGE data.
2286			if ($opmessage) {
2287				if ($this->outputIsRequested('opmessage', $options['selectRecoveryOperations'])) {
2288					foreach ($opmessage as $recovery_operationid) {
2289						$recovery_operations[$recovery_operationid]['opmessage'] = [];
2290					}
2291
2292					$db_opmessages = DBselect(
2293						'SELECT o.operationid,o.default_msg,o.subject,o.message,o.mediatypeid'.
2294						' FROM opmessage o'.
2295						' WHERE '.dbConditionInt('o.operationid', $opmessage)
2296					);
2297					while ($db_opmessage = DBfetch($db_opmessages)) {
2298						$operationid = $db_opmessage['operationid'];
2299						unset($db_opmessage['operationid']);
2300						$recovery_operations[$operationid]['opmessage'] = $db_opmessage;
2301					}
2302				}
2303
2304				if ($this->outputIsRequested('opmessage_grp', $options['selectRecoveryOperations'])) {
2305					foreach ($opmessage as $recovery_operationid) {
2306						$recovery_operations[$recovery_operationid]['opmessage_grp'] = [];
2307					}
2308
2309					$db_opmessages_grp = DBselect(
2310						'SELECT og.operationid,og.usrgrpid'.
2311						' FROM opmessage_grp og'.
2312						' WHERE '.dbConditionInt('og.operationid', $opmessage)
2313					);
2314					while ($db_opmessage_grp = DBfetch($db_opmessages_grp)) {
2315						$operationid = $db_opmessage_grp['operationid'];
2316						unset($db_opmessage_grp['operationid']);
2317						$recovery_operations[$operationid]['opmessage_grp'][] = $db_opmessage_grp;
2318					}
2319				}
2320
2321				if ($this->outputIsRequested('opmessage_usr', $options['selectRecoveryOperations'])) {
2322					foreach ($opmessage as $recovery_operationid) {
2323						$recovery_operations[$recovery_operationid]['opmessage_usr'] = [];
2324					}
2325
2326					$db_opmessages_usr = DBselect(
2327						'SELECT ou.operationid,ou.userid'.
2328						' FROM opmessage_usr ou'.
2329						' WHERE '.dbConditionInt('ou.operationid', $opmessage)
2330					);
2331					while ($db_opmessage_usr = DBfetch($db_opmessages_usr)) {
2332						$operationid = $db_opmessage_usr['operationid'];
2333						unset($db_opmessage_usr['operationid']);
2334						$recovery_operations[$operationid]['opmessage_usr'][] = $db_opmessage_usr;
2335					}
2336				}
2337			}
2338
2339			// Get OPERATION_TYPE_COMMAND data.
2340			if ($opcommand) {
2341				if ($this->outputIsRequested('opcommand', $options['selectRecoveryOperations'])) {
2342					foreach ($opcommand as $recovery_operationid) {
2343						$recovery_operations[$recovery_operationid]['opcommand'] = [];
2344					}
2345
2346					$db_opcommands = DBselect(
2347						'SELECT o.operationid,o.scriptid'.
2348						' FROM opcommand o'.
2349						' WHERE '.dbConditionInt('o.operationid', $opcommand)
2350					);
2351					while ($db_opcommand = DBfetch($db_opcommands)) {
2352						$operationid = $db_opcommand['operationid'];
2353						unset($db_opcommand['operationid']);
2354						$recovery_operations[$operationid]['opcommand'] = $db_opcommand;
2355					}
2356				}
2357
2358				if ($this->outputIsRequested('opcommand_hst', $options['selectRecoveryOperations'])) {
2359					foreach ($opcommand as $recovery_operationid) {
2360						$recovery_operations[$recovery_operationid]['opcommand_hst'] = [];
2361					}
2362
2363					$db_opcommands_hst = DBselect(
2364						'SELECT oh.opcommand_hstid,oh.operationid,oh.hostid'.
2365						' FROM opcommand_hst oh'.
2366						' WHERE '.dbConditionInt('oh.operationid', $opcommand)
2367					);
2368					while ($db_opcommand_hst = DBfetch($db_opcommands_hst)) {
2369						$operationid = $db_opcommand_hst['operationid'];
2370						unset($db_opcommand_hst['operationid']);
2371						$recovery_operations[$operationid]['opcommand_hst'][] = $db_opcommand_hst;
2372					}
2373				}
2374
2375				if ($this->outputIsRequested('opcommand_grp', $options['selectRecoveryOperations'])) {
2376					foreach ($opcommand as $recovery_operationid) {
2377						$recovery_operations[$recovery_operationid]['opcommand_grp'] = [];
2378					}
2379
2380					$db_opcommands_grp = DBselect(
2381						'SELECT og.opcommand_grpid,og.operationid,og.groupid'.
2382						' FROM opcommand_grp og'.
2383						' WHERE '.dbConditionInt('og.operationid', $opcommand)
2384					);
2385					while ($db_opcommand_grp = DBfetch($db_opcommands_grp)) {
2386						$operationid = $db_opcommand_grp['operationid'];
2387						unset($db_opcommand_grp['operationid']);
2388						$recovery_operations[$operationid]['opcommand_grp'][] = $db_opcommand_grp;
2389					}
2390				}
2391			}
2392
2393			// get OPERATION_TYPE_RECOVERY_MESSAGE data
2394			if ($op_recovery_message) {
2395				if ($this->outputIsRequested('opmessage', $options['selectRecoveryOperations'])) {
2396					foreach ($op_recovery_message as $operationid) {
2397						$recovery_operations[$operationid]['opmessage'] = [];
2398					}
2399
2400					$db_opmessages = DBselect(
2401						'SELECT o.operationid,o.default_msg,o.subject,o.message,o.mediatypeid'.
2402						' FROM opmessage o'.
2403						' WHERE '.dbConditionInt('o.operationid', $op_recovery_message)
2404					);
2405					while ($db_opmessage = DBfetch($db_opmessages)) {
2406						$operationid = $db_opmessage['operationid'];
2407						unset($db_opmessage['operationid']);
2408						$recovery_operations[$operationid]['opmessage'] = $db_opmessage;
2409					}
2410				}
2411			}
2412
2413			$recovery_operations = $this->unsetExtraFields($recovery_operations,
2414				['operationid', 'actionid', 'operationtype'], $options['selectRecoveryOperations']
2415			);
2416			$result = $relationMap->mapMany($result, $recovery_operations, 'recoveryOperations');
2417		}
2418
2419		return $result;
2420	}
2421
2422	/**
2423	 * Returns array of acknowledge operations according to requested options.
2424	 *
2425	 * @param array $ack_operations		Array of acknowledge operation with key set to operationid.
2426	 * @param array $ack_options		Array of acknowledge operation options from request.
2427	 *
2428	 * @return array
2429	 */
2430	protected function getAcknowledgeOperations($ack_operations, $ack_options) {
2431		$opmessages = [];
2432		$nonack_messages = [];
2433		$opcommands = [];
2434
2435		foreach ($ack_operations as $ack_operationid => &$ack_operation) {
2436			unset($ack_operation['esc_period'], $ack_operation['esc_step_from'], $ack_operation['esc_step_to']);
2437
2438			switch ($ack_operation['operationtype']) {
2439				case OPERATION_TYPE_ACK_MESSAGE:
2440					$opmessages[] = $ack_operationid;
2441					break;
2442				case OPERATION_TYPE_MESSAGE:
2443					$opmessages[] = $ack_operationid;
2444					$nonack_messages[] = $ack_operationid;
2445					break;
2446				case OPERATION_TYPE_COMMAND:
2447					$opcommands[] = $ack_operationid;
2448					break;
2449			}
2450		}
2451		unset($ack_operation);
2452
2453		if ($opmessages) {
2454			if ($this->outputIsRequested('opmessage', $ack_options)) {
2455				foreach ($opmessages as $operationid) {
2456					$ack_operations[$operationid]['opmessage'] = [];
2457				}
2458
2459				$db_opmessages = DBselect(
2460					'SELECT o.operationid,o.default_msg,o.subject,o.message,o.mediatypeid'.
2461					' FROM opmessage o'.
2462					' WHERE '.dbConditionInt('o.operationid', $opmessages)
2463				);
2464				while ($db_opmessage = DBfetch($db_opmessages)) {
2465					$operationid = $db_opmessage['operationid'];
2466					unset($db_opmessage['operationid']);
2467					$ack_operations[$operationid]['opmessage'] = $db_opmessage;
2468				}
2469			}
2470
2471			if ($this->outputIsRequested('opmessage_grp', $ack_options) && $nonack_messages) {
2472				foreach ($nonack_messages as $operationid) {
2473					$ack_operations[$operationid]['opmessage_grp'] = [];
2474				}
2475
2476				$db_opmessage_grp = DBselect(
2477					'SELECT og.operationid,og.usrgrpid'.
2478					' FROM opmessage_grp og'.
2479					' WHERE '.dbConditionInt('og.operationid', $nonack_messages)
2480				);
2481				while ($opmessage_grp = DBfetch($db_opmessage_grp)) {
2482					$operationid = $opmessage_grp['operationid'];
2483					unset($opmessage_grp['operationid']);
2484					$ack_operations[$operationid]['opmessage_grp'][] = $opmessage_grp;
2485				}
2486			}
2487
2488			if ($this->outputIsRequested('opmessage_usr', $ack_options) && $nonack_messages) {
2489				foreach ($nonack_messages as $operationid) {
2490					$ack_operations[$operationid]['opmessage_usr'] = [];
2491				}
2492
2493				$db_opmessage_usr = DBselect(
2494					'SELECT ou.operationid,ou.userid'.
2495					' FROM opmessage_usr ou'.
2496					' WHERE '.dbConditionInt('ou.operationid', $nonack_messages)
2497				);
2498				while ($opmessage_usr = DBfetch($db_opmessage_usr)) {
2499					$operationid = $opmessage_usr['operationid'];
2500					unset($opmessage_usr['operationid']);
2501					$ack_operations[$operationid]['opmessage_usr'][] = $opmessage_usr;
2502				}
2503			}
2504		}
2505
2506		if ($opcommands) {
2507			if ($this->outputIsRequested('opcommand', $ack_options)) {
2508				foreach ($opcommands as $operationid) {
2509					$ack_operations[$operationid]['opcommand'] = [];
2510				}
2511
2512				$db_opcommands = DBselect(
2513					'SELECT o.operationid,o.scriptid'.
2514					' FROM opcommand o'.
2515					' WHERE '.dbConditionInt('o.operationid', $opcommands)
2516				);
2517				while ($db_opcommand = DBfetch($db_opcommands)) {
2518					$operationid = $db_opcommand['operationid'];
2519					unset($db_opcommand['operationid']);
2520					$ack_operations[$operationid]['opcommand'] = $db_opcommand;
2521				}
2522			}
2523
2524			if ($this->outputIsRequested('opcommand_hst', $ack_options)) {
2525				foreach ($opcommands as $operationid) {
2526					$ack_operations[$operationid]['opcommand_hst'] = [];
2527				}
2528
2529				$db_opcommand_hst = DBselect(
2530					'SELECT oh.opcommand_hstid,oh.operationid,oh.hostid'.
2531					' FROM opcommand_hst oh'.
2532					' WHERE '.dbConditionInt('oh.operationid', $opcommands)
2533				);
2534				while ($opcommand_hst = DBfetch($db_opcommand_hst)) {
2535					$operationid = $opcommand_hst['operationid'];
2536					unset($opcommand_hst['operationid']);
2537					$ack_operations[$operationid]['opcommand_hst'][] = $opcommand_hst;
2538				}
2539			}
2540
2541			if ($this->outputIsRequested('opcommand_grp', $ack_options)) {
2542				foreach ($opcommands as $operationid) {
2543					$ack_operations[$operationid]['opcommand_grp'] = [];
2544				}
2545
2546				$db_opcommand_grp = DBselect(
2547					'SELECT og.opcommand_grpid,og.operationid,og.groupid'.
2548					' FROM opcommand_grp og'.
2549					' WHERE '.dbConditionInt('og.operationid', $opcommands)
2550				);
2551				while ($opcommand_grp = DBfetch($db_opcommand_grp)) {
2552					$operationid = $opcommand_grp['operationid'];
2553					unset($opcommand_grp['operationid']);
2554					$ack_operations[$operationid]['opcommand_grp'][] = $opcommand_grp;
2555				}
2556			}
2557		}
2558
2559		$ack_operations = $this->unsetExtraFields($ack_operations, ['operationid', 'operationtype'],
2560			$ack_options
2561		);
2562
2563		return $ack_operations;
2564	}
2565
2566	/**
2567	 * Returns the parameters for creating a discovery rule filter validator.
2568	 *
2569	 * @return array
2570	 */
2571	protected function getFilterSchema() {
2572		return [
2573			'validators' => [
2574				'evaltype' => new CLimitedSetValidator([
2575					'values' => [
2576						CONDITION_EVAL_TYPE_OR,
2577						CONDITION_EVAL_TYPE_AND,
2578						CONDITION_EVAL_TYPE_AND_OR,
2579						CONDITION_EVAL_TYPE_EXPRESSION
2580					],
2581					'messageInvalid' => _('Incorrect type of calculation for action "%1$s".')
2582				]),
2583				'formula' => new CStringValidator([
2584					'empty' => true
2585				]),
2586				'conditions' => new CCollectionValidator([
2587					'empty' => true,
2588					'messageInvalid' => _('Incorrect conditions for action "%1$s".')
2589				])
2590			],
2591			'postValidators' => [
2592				new CConditionValidator([
2593					'messageMissingFormula' => _('Formula missing for action "%1$s".'),
2594					'messageInvalidFormula' => _('Incorrect custom expression "%2$s" for action "%1$s": %3$s.'),
2595					'messageMissingCondition' => _('Condition "%2$s" used in formula "%3$s" for action "%1$s" is not defined.'),
2596					'messageUnusedCondition' => _('Condition "%2$s" is not used in formula "%3$s" for action "%1$s".'),
2597					'messageAndWithSeveralTriggers' => _('Comparing several triggers with "and" is not allowed.')
2598				])
2599			],
2600			'required' => ['evaltype', 'conditions'],
2601			'messageRequired' => _('No "%2$s" given for the filter of action "%1$s".'),
2602			'messageUnsupported' => _('Unsupported parameter "%2$s" for the filter of action "%1$s".')
2603		];
2604	}
2605
2606	/**
2607	 * Returns the parameters for creating a action filter condition validator.
2608	 *
2609	 * @return array
2610	 */
2611	protected function getFilterConditionSchema() {
2612		return [
2613			'validators' => [
2614				'conditiontype' => new CStringValidator([
2615					'regex' => '/\d+/',
2616					'messageEmpty' => _('Empty filter condition type for action "%1$s".'),
2617					'messageRegex' => _('Incorrect filter condition type for action "%1$s".')
2618				]),
2619				'value' => new CStringValidator([
2620					'empty' => true
2621				]),
2622				'value2' => new CStringValidator([
2623					'empty' => true
2624				]),
2625				'formulaid' => new CStringValidator([
2626					'regex' => '/[A-Z]+/',
2627					'messageEmpty' => _('Empty filter condition formula ID for action "%1$s".'),
2628					'messageRegex' => _('Incorrect filter condition formula ID for action "%1$s".')
2629				]),
2630				'operator' => new CStringValidator([
2631					'regex' => '/\d+/',
2632					'messageEmpty' => _('Empty filter condition operator for action "%1$s".'),
2633					'messageRegex' => _('Incorrect filter condition operator for action "%1$s".')
2634				])
2635			],
2636			'required' => ['conditiontype', 'value'],
2637			'postValidators' => [
2638				new CActionCondValidator()
2639			],
2640			'messageRequired' => _('No "%2$s" given for a filter condition of action "%1$s".'),
2641			'messageUnsupported' => _('Unsupported parameter "%2$s" for a filter condition of action "%1$s".')
2642		];
2643	}
2644
2645	protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) {
2646		$sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts);
2647
2648		if (!$options['countOutput']) {
2649			// add filter fields
2650			if ($this->outputIsRequested('formula', $options['selectFilter'])
2651				|| $this->outputIsRequested('eval_formula', $options['selectFilter'])
2652				|| $this->outputIsRequested('conditions', $options['selectFilter'])) {
2653
2654				$sqlParts = $this->addQuerySelect('a.formula', $sqlParts);
2655				$sqlParts = $this->addQuerySelect('a.evaltype', $sqlParts);
2656			}
2657			if ($this->outputIsRequested('evaltype', $options['selectFilter'])) {
2658				$sqlParts = $this->addQuerySelect('a.evaltype', $sqlParts);
2659			}
2660		}
2661
2662		return $sqlParts;
2663	}
2664
2665	/**
2666	 * Converts a formula with letters to a formula with IDs and updates it.
2667	 *
2668	 * @param string 	$actionId
2669	 * @param string 	$formulaWithLetters		formula with letters
2670	 * @param array 	$conditions
2671	 */
2672	protected function updateFormula($actionId, $formulaWithLetters, array $conditions) {
2673		$formulaIdToConditionId = [];
2674
2675		foreach ($conditions as $condition) {
2676			$formulaIdToConditionId[$condition['formulaid']] = $condition['conditionid'];
2677		}
2678		$formula = CConditionHelper::replaceLetterIds($formulaWithLetters, $formulaIdToConditionId);
2679
2680		DB::updateByPk('actions', $actionId, ['formula' => $formula]);
2681	}
2682
2683	/**
2684	 * Validate input given to action.create API call.
2685	 *
2686	 * @param $actions
2687	 */
2688	protected function validateCreate($actions) {
2689		if (!$actions) {
2690			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
2691		}
2692
2693		$actionDbFields = [
2694			'name'        => null,
2695			'eventsource' => null
2696		];
2697
2698		$duplicates = [];
2699
2700		foreach ($actions as $action) {
2701			if (!check_db_fields($actionDbFields, $action)) {
2702				self::exception(
2703					ZBX_API_ERROR_PARAMETERS,
2704					_s('Incorrect parameter for action "%1$s".', $action['name'])
2705				);
2706			}
2707
2708			if (isset($duplicates[$action['name']])) {
2709				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Action "%1$s" already exists.', $action['name']));
2710			}
2711			else {
2712				$duplicates[$action['name']] = $action['name'];
2713			}
2714
2715			if (array_key_exists('esc_period', $action) && $action['eventsource'] == EVENT_SOURCE_TRIGGERS) {
2716				self::validateStepDuration($action['esc_period']);
2717			}
2718		}
2719
2720		$dbActionsWithSameName = $this->get([
2721			'output' => ['name'],
2722			'filter' => ['name' => $duplicates],
2723			'nopermissions' => true
2724		]);
2725		if ($dbActionsWithSameName) {
2726			$dbActionWithSameName = reset($dbActionsWithSameName);
2727			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Action "%1$s" already exists.', $dbActionWithSameName['name']));
2728		}
2729
2730		$filterValidator = new CSchemaValidator($this->getFilterSchema());
2731		$filterConditionValidator = new CSchemaValidator($this->getFilterConditionSchema());
2732		$pause_suppressed_validator = new CLimitedSetValidator([
2733			'values' => [ACTION_PAUSE_SUPPRESSED_FALSE, ACTION_PAUSE_SUPPRESSED_TRUE]
2734		]);
2735
2736		$conditionsToValidate = [];
2737		$operations_to_validate = [];
2738
2739		// Validate "filter" sections and "conditions" in them, ensure that "operations" section
2740		// is present and is not empty. Also collect conditions and operations for more validation.
2741		foreach ($actions as $action) {
2742			if ($action['eventsource'] != EVENT_SOURCE_TRIGGERS) {
2743				$this->checkNoParameters($action, ['pause_suppressed'], _('Cannot set "%1$s" for action "%2$s".'),
2744					$action['name']
2745				);
2746			}
2747			elseif (array_key_exists('pause_suppressed', $action)
2748					&& !$pause_suppressed_validator->validate($action['pause_suppressed'])) {
2749				self::exception(ZBX_API_ERROR_PARAMETERS,
2750					_s('Incorrect value "%1$s" for "%2$s" field.', $action['pause_suppressed'], 'pause_suppressed')
2751				);
2752			}
2753
2754			if (isset($action['filter'])) {
2755				$filterValidator->setObjectName($action['name']);
2756				$this->checkValidator($action['filter'], $filterValidator);
2757				$filterConditionValidator->setObjectName($action['name']);
2758
2759				foreach ($action['filter']['conditions'] as $condition) {
2760					if ($condition['conditiontype'] == CONDITION_TYPE_EVENT_TAG_VALUE &&
2761							!array_key_exists('value2', $condition)) {
2762						self::exception(
2763							ZBX_API_ERROR_PARAMETERS,
2764							_s('No "%2$s" given for a filter condition of action "%1$s".', $action['name'], 'value2')
2765						);
2766					}
2767
2768					$this->checkValidator($condition, $filterConditionValidator);
2769					$conditionsToValidate[] = $condition;
2770				}
2771
2772				$this->validateFilterConditionsIntegrity($action['name'], $action['eventsource'],
2773					$action['filter']['conditions']
2774				);
2775			}
2776
2777			if ((!array_key_exists('operations', $action) || !$action['operations'])
2778					&& (!array_key_exists('recovery_operations', $action) || !$action['recovery_operations'])
2779					&& (!array_key_exists('acknowledge_operations', $action) || !$action['acknowledge_operations'])) {
2780				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Action "%1$s" no operations defined.', $action['name']));
2781			}
2782
2783			if (array_key_exists('operations', $action)) {
2784				foreach ($action['operations'] as $operation) {
2785					if (array_key_exists('operationid', $operation)) {
2786						self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect input parameters.'));
2787					}
2788
2789					$operation['recovery'] = ACTION_OPERATION;
2790					$operation['eventsource'] = $action['eventsource'];
2791					$operations_to_validate[] = $operation;
2792				}
2793			}
2794
2795			if (array_key_exists('recovery_operations', $action)) {
2796				foreach ($action['recovery_operations'] as $operation) {
2797					if (array_key_exists('operationid', $operation)) {
2798						self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect input parameters.'));
2799					}
2800
2801					$operation['recovery'] = ACTION_RECOVERY_OPERATION;
2802					$operation['eventsource'] = $action['eventsource'];
2803					$operations_to_validate[] = $operation;
2804				}
2805			}
2806
2807			if (array_key_exists('acknowledge_operations', $action)) {
2808				foreach ($action['acknowledge_operations'] as $operation) {
2809					if (array_key_exists('operationid', $operation)) {
2810						self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect input parameters.'));
2811					}
2812
2813					$operation['recovery'] = ACTION_ACKNOWLEDGE_OPERATION;
2814					$operation['eventsource'] = $action['eventsource'];
2815					$operations_to_validate[] = $operation;
2816				}
2817			}
2818		}
2819
2820		// Validate conditions and operations in regard to what's in database now.
2821		if ($conditionsToValidate) {
2822			$this->validateConditionsPermissions($conditionsToValidate);
2823		}
2824		if ($operations_to_validate) {
2825			$this->validateOperationsIntegrity($operations_to_validate);
2826		}
2827	}
2828
2829	/**
2830	 * Validate default step duration and operation step duration values.
2831	 *
2832	 * @param string $esc_period  Step duration.
2833	 *
2834	 * @throws APIException if the input is invalid.
2835	 */
2836	private static function validateStepDuration($esc_period) {
2837		if (!validateTimeUnit($esc_period, SEC_PER_MIN, SEC_PER_WEEK, false, $error, ['usermacros' => true])) {
2838			self::exception(ZBX_API_ERROR_PARAMETERS,
2839				_s('Incorrect value for field "%1$s": %2$s.', 'esc_period', $error)
2840			);
2841		}
2842	}
2843
2844	/**
2845	 * Validate input given to action.update API call.
2846	 *
2847	 * @param array $actions
2848	 * @param array $db_actions
2849	 */
2850	protected function validateUpdate($actions, $db_actions) {
2851		foreach ($actions as $action) {
2852			if (isset($action['actionid']) && !isset($db_actions[$action['actionid']])) {
2853				self::exception(
2854					ZBX_API_ERROR_PERMISSIONS,
2855					_('No permissions to referred object or it does not exist!')
2856				);
2857			}
2858		}
2859		$actions = zbx_toHash($actions, 'actionid');
2860
2861		$pause_suppressed_validator = new CLimitedSetValidator([
2862			'values' => [ACTION_PAUSE_SUPPRESSED_FALSE, ACTION_PAUSE_SUPPRESSED_TRUE]
2863		]);
2864
2865		// check fields
2866		$duplicates = [];
2867
2868		foreach ($actions as $action) {
2869			$action_name = isset($action['name']) ? $action['name'] : $db_actions[$action['actionid']]['name'];
2870
2871			if (!check_db_fields(['actionid' => null], $action)) {
2872				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
2873					'Incorrect parameters for action update method "%1$s".', $action_name
2874				));
2875			}
2876
2877			// check if user changed esc_period for trigger eventsource
2878			if (array_key_exists('esc_period', $action)
2879					&& $db_actions[$action['actionid']]['eventsource'] == EVENT_SOURCE_TRIGGERS) {
2880				self::validateStepDuration($action['esc_period']);
2881			}
2882
2883			$this->checkNoParameters($action, ['eventsource'], _('Cannot update "%1$s" for action "%2$s".'),
2884				$action_name
2885			);
2886
2887			if ($db_actions[$action['actionid']]['eventsource'] != EVENT_SOURCE_TRIGGERS) {
2888				$this->checkNoParameters($action, ['pause_suppressed'], _('Cannot update "%1$s" for action "%2$s".'),
2889					$action_name
2890				);
2891			}
2892			elseif (array_key_exists('pause_suppressed', $action)
2893					&& !$pause_suppressed_validator->validate($action['pause_suppressed'])) {
2894				self::exception(ZBX_API_ERROR_PARAMETERS,
2895					_s('Incorrect value "%1$s" for "%2$s" field.', $action['pause_suppressed'], 'pause_suppressed')
2896				);
2897			}
2898
2899			if (!isset($action['name'])) {
2900				continue;
2901			}
2902
2903			if (isset($duplicates[$action['name']])) {
2904				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Action "%1$s" already exists.', $action['name']));
2905			}
2906			else {
2907				$duplicates[$action['name']] = $action['name'];
2908			}
2909		}
2910
2911		// Unset accidentally passed in "evaltype" and "formula" fields.
2912		foreach ($actions as &$action) {
2913			unset($action['evaltype'], $action['formula']);
2914		}
2915		unset($action);
2916
2917		$filterValidator = new CSchemaValidator($this->getFilterSchema());
2918
2919		$filterConditionValidator = new CSchemaValidator($this->getFilterConditionSchema());
2920
2921		$operations_to_validate = [];
2922		$conditionsToValidate = [];
2923
2924		foreach ($actions as $actionId => $action) {
2925			$db_action = $db_actions[$actionId];
2926
2927			if (isset($action['name'])) {
2928				$action_name = $action['name'];
2929
2930				$actionExists = $this->get([
2931					'output' => ['actionid'],
2932					'filter' => ['name' => $action_name],
2933					'nopermissions' => true
2934				]);
2935				if (($actionExists = reset($actionExists))
2936						&& (bccomp($actionExists['actionid'], $actionId) != 0)) {
2937					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Action "%1$s" already exists.', $action_name));
2938				}
2939			}
2940			else {
2941				$action_name = $db_action['name'];
2942			}
2943
2944			if (isset($action['filter'])) {
2945				$action_filter = $action['filter'];
2946
2947				$filterValidator->setObjectName($action_name);
2948				$filterConditionValidator->setObjectName($action_name);
2949
2950				$this->checkValidator($action_filter, $filterValidator);
2951
2952				foreach ($action_filter['conditions'] as $condition) {
2953					if ($condition['conditiontype'] == CONDITION_TYPE_EVENT_TAG_VALUE
2954							&& !array_key_exists('value2', $condition)) {
2955						self::exception(
2956							ZBX_API_ERROR_PARAMETERS,
2957							_s('No "%2$s" given for a filter condition of action "%1$s".', $action_name, 'value2')
2958						);
2959					}
2960
2961					$this->checkValidator($condition, $filterConditionValidator);
2962					$conditionsToValidate[] = $condition;
2963				}
2964
2965				$this->validateFilterConditionsIntegrity($action_name, $db_action['eventsource'],
2966					$action_filter['conditions']
2967				);
2968			}
2969
2970			$operations_defined = array_key_exists('operations', $action)
2971				? (bool) $action['operations']
2972				: (bool) $db_action['operations'];
2973			$rcv_operations_defined = array_key_exists('recovery_operations', $action)
2974				? (bool) $action['recovery_operations']
2975				: (bool) $db_action['recoveryOperations'];
2976			$ack_operations_defined = array_key_exists('acknowledge_operations', $action)
2977				? (bool) $action['acknowledge_operations']
2978				: (bool) $db_action['acknowledgeOperations'];
2979
2980			if (!$operations_defined && !$rcv_operations_defined && !$ack_operations_defined) {
2981				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Action "%1$s" no operations defined.', $action_name));
2982			}
2983
2984			if (array_key_exists('operations', $action) && $action['operations']) {
2985				$db_operations = zbx_toHash($db_actions[$action['actionid']]['operations'], 'operationid');
2986				foreach ($action['operations'] as $operation) {
2987					if (!array_key_exists('operationid', $operation)
2988							|| array_key_exists($operation['operationid'], $db_operations)) {
2989						$operation['recovery'] = ACTION_OPERATION;
2990						$operation['eventsource'] = $db_action['eventsource'];
2991						$operations_to_validate[] = $operation;
2992					}
2993					else {
2994						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operationid.'));
2995					}
2996				}
2997			}
2998
2999			// Recovery operations.
3000			if (array_key_exists('recovery_operations', $action) && $action['recovery_operations']) {
3001				$db_recovery_operations = zbx_toHash($db_actions[$action['actionid']]['recoveryOperations'],
3002					'operationid'
3003				);
3004				foreach ($action['recovery_operations'] as $recovery_operation) {
3005					if (!array_key_exists('operationid', $recovery_operation)
3006							|| array_key_exists($recovery_operation['operationid'], $db_recovery_operations)) {
3007						$recovery_operation['recovery'] = ACTION_RECOVERY_OPERATION;
3008						$recovery_operation['eventsource'] = $db_action['eventsource'];
3009						$operations_to_validate[] = $recovery_operation;
3010					}
3011					else {
3012						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operationid.'));
3013					}
3014				}
3015			}
3016
3017			if (array_key_exists('acknowledge_operations', $action) && $action['acknowledge_operations']) {
3018				$db_ack_operations = zbx_toHash($db_actions[$action['actionid']]['acknowledgeOperations'],
3019					'operationid'
3020				);
3021				foreach ($action['acknowledge_operations'] as $ack_operation) {
3022					if (!array_key_exists('operationid', $ack_operation)
3023							|| array_key_exists($ack_operation['operationid'], $db_ack_operations)) {
3024						$ack_operation['recovery'] = ACTION_ACKNOWLEDGE_OPERATION;
3025						$ack_operation['eventsource'] = $db_action['eventsource'];
3026
3027						if (array_key_exists('operationid', $ack_operation)
3028								&& array_key_exists($ack_operation['operationid'], $db_ack_operations)) {
3029							$db_ack_operation = $db_ack_operations[$ack_operation['operationid']];
3030							$operation_type = array_key_exists('operationtype', $ack_operation)
3031								? $ack_operation['operationtype']
3032								: $db_ack_operation['operationtype'];
3033
3034							// Field 'operationtype' is required.
3035							unset($db_ack_operation['operationtype']);
3036
3037							if ($operation_type == OPERATION_TYPE_MESSAGE) {
3038								unset($db_ack_operation['opmessage_grp'], $db_ack_operation['opmessage_usr']);
3039							}
3040							elseif ($operation_type == OPERATION_TYPE_COMMAND) {
3041								unset($db_ack_operation['opcommand_grp'], $db_ack_operation['opcommand_hst']);
3042							}
3043
3044							$ack_operation += $db_ack_operation;
3045						}
3046
3047						$operations_to_validate[] = $ack_operation;
3048					}
3049					else {
3050						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect acknowledgement action operationid.'));
3051					}
3052				}
3053			}
3054		}
3055
3056		if ($conditionsToValidate) {
3057			$this->validateConditionsPermissions($conditionsToValidate);
3058		}
3059		$this->validateOperationsIntegrity($operations_to_validate);
3060	}
3061
3062	/**
3063	 * Check permissions to DB entities referenced by action conditions.
3064	 *
3065	 * @param array $conditions   conditions for which permissions to referenced DB entities will be checked
3066	 */
3067	protected function validateConditionsPermissions(array $conditions) {
3068		$hostGroupIdsAll = [];
3069		$templateIdsAll = [];
3070		$triggerIdsAll = [];
3071		$hostIdsAll = [];
3072		$discoveryRuleIdsAll = [];
3073		$discoveryCheckIdsAll = [];
3074		$proxyIdsAll = [];
3075
3076		foreach ($conditions as $condition) {
3077			$conditionValue = $condition['value'];
3078			// validate condition values depending on condition type
3079			switch ($condition['conditiontype']) {
3080				case CONDITION_TYPE_HOST_GROUP:
3081					$hostGroupIdsAll[$conditionValue] = $conditionValue;
3082					break;
3083
3084				case CONDITION_TYPE_TEMPLATE:
3085					$templateIdsAll[$conditionValue] = $conditionValue;
3086					break;
3087
3088				case CONDITION_TYPE_TRIGGER:
3089					$triggerIdsAll[$conditionValue] = $conditionValue;
3090					break;
3091
3092				case CONDITION_TYPE_HOST:
3093					$hostIdsAll[$conditionValue] = $conditionValue;
3094					break;
3095
3096				case CONDITION_TYPE_DRULE:
3097					$discoveryRuleIdsAll[$conditionValue] = $conditionValue;
3098					break;
3099
3100				case CONDITION_TYPE_DCHECK:
3101					$discoveryCheckIdsAll[$conditionValue] = $conditionValue;
3102					break;
3103
3104				case CONDITION_TYPE_PROXY:
3105					$proxyIdsAll[$conditionValue] = $conditionValue;
3106					break;
3107			}
3108		}
3109
3110		$this->checkHostGroupsPermissions($hostGroupIdsAll,
3111			_('Incorrect action condition host group. Host group does not exist or you have no access to it.')
3112		);
3113		$this->checkHostsPermissions($hostIdsAll,
3114			_('Incorrect action condition host. Host does not exist or you have no access to it.')
3115		);
3116		$this->checkTemplatesPermissions($templateIdsAll,
3117			_('Incorrect action condition template. Template does not exist or you have no access to it.')
3118		);
3119		$this->checkTriggersPermissions($triggerIdsAll,
3120			_('Incorrect action condition trigger. Trigger does not exist or you have no access to it.')
3121		);
3122		$this->checkDRulesPermissions($discoveryRuleIdsAll,
3123			_('Incorrect action condition discovery rule. Discovery rule does not exist or you have no access to it.')
3124		);
3125		$this->checkDChecksPermissions($discoveryCheckIdsAll,
3126			_('Incorrect action condition discovery check. Discovery check does not exist or you have no access to it.')
3127		);
3128		$this->checkProxiesPermissions($proxyIdsAll,
3129			_('Incorrect action condition proxy. Proxy does not exist or you have no access to it.')
3130		);
3131	}
3132
3133	/**
3134	 * Checks if all given media types are valid.
3135	 *
3136	 * @param array  $mediatypeids  Array of media type ids where key is checked media type id.
3137	 * @param string $error         Error message to throw if invalid media type id was supplied.
3138	 *
3139	 * @throws APIException if invalid media types given.
3140	 */
3141	private function checkMediatypesExists(array $mediatypeids, $error) {
3142		if ($mediatypeids) {
3143			$count = API::MediaType()->get([
3144				'countOutput' => true,
3145				'mediatypeids' => array_keys($mediatypeids)
3146			]);
3147
3148			if ($count != count($mediatypeids)) {
3149				self::exception(ZBX_API_ERROR_PERMISSIONS, $error);
3150			}
3151		}
3152	}
3153
3154	/**
3155	 * Checks if the current user has access to the given host groups.
3156	 *
3157	 * @throws APIException if the user doesn't have write permissions for the given host groups
3158	 *
3159	 * @param  array     $groupids
3160	 * @param  string    $error
3161	 */
3162	private function checkHostGroupsPermissions(array $groupids, $error) {
3163		if ($groupids) {
3164			$groupids = array_unique($groupids);
3165
3166			$count = API::HostGroup()->get([
3167				'countOutput' => true,
3168				'groupids' => $groupids,
3169				'editable' => true
3170			]);
3171
3172			if ($count != count($groupids)) {
3173				self::exception(ZBX_API_ERROR_PERMISSIONS, $error);
3174			}
3175		}
3176	}
3177
3178	/**
3179	 * Checks if the current user has access to the given hosts.
3180	 *
3181	 * @throws APIException if the user doesn't have write permissions for the given hosts
3182	 *
3183	 * @param  array     $hostids
3184	 * @param  string    $error
3185	 */
3186	private function checkHostsPermissions(array $hostids, $error) {
3187		if ($hostids) {
3188			$hostids = array_unique($hostids);
3189
3190			$count = API::Host()->get([
3191				'countOutput' => true,
3192				'hostids' => $hostids,
3193				'editable' => true
3194			]);
3195
3196			if ($count != count($hostids)) {
3197				self::exception(ZBX_API_ERROR_PERMISSIONS, $error);
3198			}
3199		}
3200	}
3201
3202	/**
3203	 * Checks if the current user has access to the given users.
3204	 *
3205	 * @throws APIException if the user doesn't have write permissions for the given users
3206	 *
3207	 * @param array  $userids
3208	 * @param string $error
3209	 */
3210	protected function checkUsersPermissions(array $userids, $error) {
3211		if ($userids) {
3212			$userids = array_unique($userids);
3213
3214			$count = API::User()->get([
3215				'countOutput' => true,
3216				'userids' => $userids
3217			]);
3218
3219			if ($count != count($userids)) {
3220				self::exception(ZBX_API_ERROR_PERMISSIONS, $error);
3221			}
3222		}
3223	}
3224
3225	/**
3226	 * Checks if the current user has access to the given user groups.
3227	 *
3228	 * @throws APIException if the user doesn't have write permissions for the given user groups
3229	 *
3230	 * @param array  $usrgrpids
3231	 * @param string $error
3232	 */
3233	protected function checkUserGroupsPermissions(array $usrgrpids, $error) {
3234		if ($usrgrpids) {
3235			$usrgrpids = array_unique($usrgrpids);
3236
3237			$count = API::UserGroup()->get([
3238				'countOutput' => true,
3239				'usrgrpids' => $usrgrpids
3240			]);
3241
3242			if ($count != count($usrgrpids)) {
3243				self::exception(ZBX_API_ERROR_PERMISSIONS, $error);
3244			}
3245		}
3246	}
3247
3248	/**
3249	 * Checks if the current user has access to the given templates.
3250	 *
3251	 * @throws APIException if the user doesn't have write permissions for the given templates
3252	 *
3253	 * @param array  $templateids
3254	 * @param string $error
3255	 */
3256	protected function checkTemplatesPermissions(array $templateids, $error) {
3257		if ($templateids) {
3258			$templateids = array_unique($templateids);
3259
3260			$count = API::Template()->get([
3261				'countOutput' => true,
3262				'templateids' => $templateids,
3263				'editable' => true
3264			]);
3265
3266			if ($count != count($templateids)) {
3267				self::exception(ZBX_API_ERROR_PERMISSIONS, $error);
3268			}
3269		}
3270	}
3271
3272	/**
3273	 * Checks if the current user has access to the given triggers.
3274	 *
3275	 * @throws APIException if the user doesn't have write permissions for the given triggers
3276	 *
3277	 * @param array  $triggerids
3278	 * @param string $error
3279	 */
3280	protected function checkTriggersPermissions(array $triggerids, $error) {
3281		if ($triggerids) {
3282			$triggerids = array_unique($triggerids);
3283
3284			$count = API::Trigger()->get([
3285				'countOutput' => true,
3286				'triggerids' => $triggerids,
3287				'editable' => true
3288			]);
3289
3290			if ($count != count($triggerids)) {
3291				self::exception(ZBX_API_ERROR_PERMISSIONS, $error);
3292			}
3293		}
3294	}
3295
3296	/**
3297	 * Checks if the current user has access to the given discovery rules.
3298	 *
3299	 * @throws APIException if the user doesn't have write permissions for the given discovery rules
3300	 *
3301	 * @param array  $druleids
3302	 * @param string $error
3303	 */
3304	protected function checkDRulesPermissions(array $druleids, $error) {
3305		if ($druleids) {
3306			$druleids = array_unique($druleids);
3307
3308			$count = API::DRule()->get([
3309				'countOutput' => true,
3310				'druleids' => $druleids,
3311				'editable' => true
3312			]);
3313
3314			if ($count != count($druleids)) {
3315				self::exception(ZBX_API_ERROR_PERMISSIONS, $error);
3316			}
3317		}
3318	}
3319
3320	/**
3321	 * Checks if the current user has access to the given discovery checks.
3322	 *
3323	 * @throws APIException if the user doesn't have write permissions for the given discovery checks
3324	 *
3325	 * @param array  $dcheckids
3326	 * @param string $error
3327	 */
3328	protected function checkDChecksPermissions(array $dcheckids, $error) {
3329		if ($dcheckids) {
3330			$dcheckids = array_unique($dcheckids);
3331
3332			$count = API::DCheck()->get([
3333				'countOutput' => true,
3334				'dcheckids' => $dcheckids,
3335				'editable' => true
3336			]);
3337
3338			if ($count != count($dcheckids)) {
3339				self::exception(ZBX_API_ERROR_PERMISSIONS, $error);
3340			}
3341		}
3342	}
3343
3344	/**
3345	 * Checks if the current user has access to the given proxies.
3346	 *
3347	 * @throws APIException if the user doesn't have write permissions for the given proxies
3348	 *
3349	 * @param array  $proxyids
3350	 * @param string $error
3351	 */
3352	protected function checkProxiesPermissions(array $proxyids, $error) {
3353		if ($proxyids) {
3354			$proxyids = array_unique($proxyids);
3355
3356			$count = API::Proxy()->get([
3357				'countOutput' => true,
3358				'proxyids' => $proxyids,
3359				'editable' => true
3360			]);
3361
3362			if ($count != count($proxyids)) {
3363				self::exception(ZBX_API_ERROR_PERMISSIONS, $error);
3364			}
3365		}
3366	}
3367}
3368