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