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