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 *
25 * @package API
26 */
27class CAction extends CApiService {
28
29	protected $tableName = 'actions';
30	protected $tableAlias = 'a';
31	protected $sortColumns = ['actionid', 'name', 'status'];
32
33	/**
34	 * Get actions data
35	 *
36	 * @param array $options
37	 * @param array $options['itemids']
38	 * @param array $options['hostids']
39	 * @param array $options['groupids']
40	 * @param array $options['actionids']
41	 * @param array $options['applicationids']
42	 * @param array $options['status']
43	 * @param bool  $options['editable']
44	 * @param array $options['extendoutput']
45	 * @param array $options['count']
46	 * @param array $options['pattern']
47	 * @param array $options['limit']
48	 * @param array $options['order']
49	 *
50	 * @return array|int item data as array or false if error
51	 */
52	public function get($options = []) {
53		$result = [];
54
55		$sqlParts = [
56			'select'	=> ['actions' => 'a.actionid'],
57			'from'		=> ['actions' => 'actions a'],
58			'where'		=> [],
59			'order'		=> [],
60			'limit'		=> null
61		];
62
63		$defOptions = [
64			'groupids'					=> null,
65			'hostids'					=> null,
66			'actionids'					=> null,
67			'triggerids'				=> null,
68			'mediatypeids'				=> null,
69			'usrgrpids'					=> null,
70			'userids'					=> null,
71			'scriptids'					=> null,
72			'nopermissions'				=> null,
73			'editable'					=> false,
74			// filter
75			'filter'					=> null,
76			'search'					=> null,
77			'searchByAny'				=> null,
78			'startSearch'				=> null,
79			'excludeSearch'				=> null,
80			'searchWildcardsEnabled'	=> null,
81			// output
82			'output'					=> API_OUTPUT_EXTEND,
83			'selectFilter'				=> null,
84			'selectOperations'			=> null,
85			'countOutput'				=> null,
86			'preservekeys'				=> null,
87			'sortfield'					=> '',
88			'sortorder'					=> '',
89			'limit'						=> null
90		];
91		$options = zbx_array_merge($defOptions, $options);
92
93		// editable + PERMISSION CHECK
94		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) {
95			// conditions are checked here by sql, operations after, by api queries
96			$permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ;
97			$userGroups = getUserGroupsByUserId(self::$userData['userid']);
98
99			// condition hostgroup
100			$sqlParts['where'][] = 'NOT EXISTS ('.
101					'SELECT NULL'.
102					' FROM conditions cc'.
103						' LEFT JOIN rights r'.
104							' ON r.id='.zbx_dbcast_2bigint('cc.value').
105								' AND '.dbConditionInt('r.groupid', $userGroups).
106					' WHERE a.actionid=cc.actionid'.
107						' AND cc.conditiontype='.CONDITION_TYPE_HOST_GROUP.
108					' GROUP BY cc.value'.
109					' HAVING MIN(r.permission) IS NULL'.
110						' OR MIN(r.permission)='.PERM_DENY.
111						' OR MAX(r.permission)<'.zbx_dbstr($permission).
112					')';
113
114			// condition host or template
115			$sqlParts['where'][] = 'NOT EXISTS ('.
116					'SELECT NULL'.
117					' FROM conditions cc,hosts_groups hgg'.
118						' LEFT JOIN rights r'.
119							' ON r.id=hgg.groupid'.
120								' AND '.dbConditionInt('r.groupid', $userGroups).
121					' WHERE a.actionid=cc.actionid'.
122						' AND '.zbx_dbcast_2bigint('cc.value').'=hgg.hostid'.
123						' AND cc.conditiontype IN ('.CONDITION_TYPE_HOST.','.CONDITION_TYPE_TEMPLATE.')'.
124					' GROUP BY cc.value'.
125					' HAVING MIN(r.permission) IS NULL'.
126						' OR MIN(r.permission)='.PERM_DENY.
127						' OR MAX(r.permission)<'.zbx_dbstr($permission).
128					')';
129
130			// condition trigger
131			$sqlParts['where'][] = 'NOT EXISTS ('.
132					'SELECT NULL'.
133					' FROM conditions cc,functions f,items i,hosts_groups hgg'.
134						' LEFT JOIN rights r'.
135							' ON r.id=hgg.groupid'.
136								' AND '.dbConditionInt('r.groupid', $userGroups).
137					' WHERE a.actionid=cc.actionid'.
138						' AND '.zbx_dbcast_2bigint('cc.value').'=f.triggerid'.
139						' AND f.itemid=i.itemid'.
140						' AND i.hostid=hgg.hostid'.
141						' AND cc.conditiontype='.CONDITION_TYPE_TRIGGER.
142					' GROUP BY cc.value'.
143					' HAVING MIN(r.permission) IS NULL'.
144						' OR MIN(r.permission)='.PERM_DENY.
145						' OR MAX(r.permission)<'.zbx_dbstr($permission).
146					')';
147		}
148
149		// actionids
150		if (!is_null($options['actionids'])) {
151			zbx_value2array($options['actionids']);
152
153			$sqlParts['where'][] = dbConditionInt('a.actionid', $options['actionids']);
154		}
155
156		// groupids
157		if (!is_null($options['groupids'])) {
158			zbx_value2array($options['groupids']);
159
160			$sqlParts['from']['conditions_groups'] = 'conditions cg';
161			$sqlParts['where'][] = dbConditionString('cg.value', $options['groupids']);
162			$sqlParts['where']['ctg'] = 'cg.conditiontype='.CONDITION_TYPE_HOST_GROUP;
163			$sqlParts['where']['acg'] = 'a.actionid=cg.actionid';
164		}
165
166		// hostids
167		if (!is_null($options['hostids'])) {
168			zbx_value2array($options['hostids']);
169
170			$sqlParts['from']['conditions_hosts'] = 'conditions ch';
171			$sqlParts['where'][] = dbConditionString('ch.value', $options['hostids']);
172			$sqlParts['where']['cth'] = 'ch.conditiontype='.CONDITION_TYPE_HOST;
173			$sqlParts['where']['ach'] = 'a.actionid=ch.actionid';
174		}
175
176		// triggerids
177		if (!is_null($options['triggerids'])) {
178			zbx_value2array($options['triggerids']);
179
180			$sqlParts['from']['conditions_triggers'] = 'conditions ct';
181			$sqlParts['where'][] = dbConditionString('ct.value', $options['triggerids']);
182			$sqlParts['where']['ctt'] = 'ct.conditiontype='.CONDITION_TYPE_TRIGGER;
183			$sqlParts['where']['act'] = 'a.actionid=ct.actionid';
184		}
185
186		// mediatypeids
187		if (!is_null($options['mediatypeids'])) {
188			zbx_value2array($options['mediatypeids']);
189
190			$sqlParts['from']['opmessage'] = 'opmessage om';
191			$sqlParts['from']['operations_media'] = 'operations omed';
192			$sqlParts['where'][] = dbConditionInt('om.mediatypeid', $options['mediatypeids']);
193			$sqlParts['where']['aomed'] = 'a.actionid=omed.actionid';
194			$sqlParts['where']['oom'] = 'omed.operationid=om.operationid';
195		}
196
197		// operation messages
198		// usrgrpids
199		if (!is_null($options['usrgrpids'])) {
200			zbx_value2array($options['usrgrpids']);
201
202			$sqlParts['from']['opmessage_grp'] = 'opmessage_grp omg';
203			$sqlParts['from']['operations_usergroups'] = 'operations oug';
204			$sqlParts['where'][] = dbConditionInt('omg.usrgrpid', $options['usrgrpids']);
205			$sqlParts['where']['aoug'] = 'a.actionid=oug.actionid';
206			$sqlParts['where']['oomg'] = 'oug.operationid=omg.operationid';
207		}
208
209		// userids
210		if (!is_null($options['userids'])) {
211			zbx_value2array($options['userids']);
212
213			$sqlParts['from']['opmessage_usr'] = 'opmessage_usr omu';
214			$sqlParts['from']['operations_users'] = 'operations ou';
215			$sqlParts['where'][] = dbConditionInt('omu.userid', $options['userids']);
216			$sqlParts['where']['aou'] = 'a.actionid=ou.actionid';
217			$sqlParts['where']['oomu'] = 'ou.operationid=omu.operationid';
218		}
219
220		// operation commands
221		// scriptids
222		if (!is_null($options['scriptids'])) {
223			zbx_value2array($options['scriptids']);
224
225			$sqlParts['from']['opcommand'] = 'opcommand oc';
226			$sqlParts['from']['operations_scripts'] = 'operations os';
227			$sqlParts['where'][] = '('.dbConditionInt('oc.scriptid', $options['scriptids']).
228				' AND oc.type='.ZBX_SCRIPT_TYPE_GLOBAL_SCRIPT.')';
229			$sqlParts['where']['aos'] = 'a.actionid=os.actionid';
230			$sqlParts['where']['ooc'] = 'os.operationid=oc.operationid';
231		}
232
233		// filter
234		if (is_array($options['filter'])) {
235			$this->dbFilter('actions a', $options, $sqlParts);
236		}
237
238		// search
239		if (is_array($options['search'])) {
240			zbx_db_search('actions a', $options, $sqlParts);
241		}
242
243		// limit
244		if (zbx_ctype_digit($options['limit']) && $options['limit']) {
245			$sqlParts['limit'] = $options['limit'];
246		}
247
248		$actionIds = [];
249
250		$sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
251		$sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
252		$dbRes = DBselect($this->createSelectQueryFromParts($sqlParts), $sqlParts['limit']);
253		while ($action = DBfetch($dbRes)) {
254			if ($options['countOutput']) {
255				$result = $action['rowscount'];
256			}
257			else {
258				$actionIds[$action['actionid']] = $action['actionid'];
259
260				$result[$action['actionid']] = $action;
261			}
262		}
263
264		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) {
265			// check hosts, templates
266			$hosts = [];
267			$hostIds = [];
268			$sql = 'SELECT o.actionid,och.hostid'.
269					' FROM operations o,opcommand_hst och'.
270					' WHERE o.operationid=och.operationid'.
271						' AND och.hostid<>0'.
272						' AND '.dbConditionInt('o.actionid', $actionIds);
273			$dbHosts = DBselect($sql);
274			while ($host = DBfetch($dbHosts)) {
275				if (!isset($hosts[$host['hostid']])) {
276					$hosts[$host['hostid']] = [];
277				}
278				$hosts[$host['hostid']][$host['actionid']] = $host['actionid'];
279				$hostIds[$host['hostid']] = $host['hostid'];
280			}
281
282			$dbTemplates = DBselect(
283				'SELECT o.actionid,ot.templateid'.
284				' FROM operations o,optemplate ot'.
285				' WHERE o.operationid=ot.operationid'.
286					' AND '.dbConditionInt('o.actionid', $actionIds)
287			);
288			while ($template = DBfetch($dbTemplates)) {
289				if (!isset($hosts[$template['templateid']])) {
290					$hosts[$template['templateid']] = [];
291				}
292				$hosts[$template['templateid']][$template['actionid']] = $template['actionid'];
293				$hostIds[$template['templateid']] = $template['templateid'];
294			}
295
296			$allowedHosts = API::Host()->get([
297				'hostids' => $hostIds,
298				'output' => ['hostid'],
299				'editable' => $options['editable'],
300				'templated_hosts' => true,
301				'preservekeys' => true
302			]);
303			foreach ($hostIds as $hostId) {
304				if (isset($allowedHosts[$hostId])) {
305					continue;
306				}
307				foreach ($hosts[$hostId] as $actionId) {
308					unset($result[$actionId], $actionIds[$actionId]);
309				}
310			}
311			unset($allowedHosts);
312
313			// check hostgroups
314			$groups = [];
315			$groupIds = [];
316			$dbGroups = DBselect(
317				'SELECT o.actionid,ocg.groupid'.
318				' FROM operations o,opcommand_grp ocg'.
319				' WHERE o.operationid=ocg.operationid'.
320					' AND '.dbConditionInt('o.actionid', $actionIds)
321			);
322			while ($group = DBfetch($dbGroups)) {
323				if (!isset($groups[$group['groupid']])) {
324					$groups[$group['groupid']] = [];
325				}
326				$groups[$group['groupid']][$group['actionid']] = $group['actionid'];
327				$groupIds[$group['groupid']] = $group['groupid'];
328			}
329
330			$dbGroups = DBselect(
331				'SELECT o.actionid,og.groupid'.
332				' FROM operations o,opgroup og'.
333				' WHERE o.operationid=og.operationid'.
334					' AND '.dbConditionInt('o.actionid', $actionIds)
335			);
336			while ($group = DBfetch($dbGroups)) {
337				if (!isset($groups[$group['groupid']])) {
338					$groups[$group['groupid']] = [];
339				}
340				$groups[$group['groupid']][$group['actionid']] = $group['actionid'];
341				$groupIds[$group['groupid']] = $group['groupid'];
342			}
343
344			$allowedGroups = API::HostGroup()->get([
345				'groupids' => $groupIds,
346				'output' => ['groupid'],
347				'editable' => $options['editable'],
348				'preservekeys' => true
349			]);
350			foreach ($groupIds as $groupId) {
351				if (isset($allowedGroups[$groupId])) {
352					continue;
353				}
354				foreach ($groups[$groupId] as $actionId) {
355					unset($result[$actionId], $actionIds[$actionId]);
356				}
357			}
358			unset($allowedGroups);
359
360			// check scripts
361			$scripts = [];
362			$scriptIds = [];
363			$dbScripts = DBselect(
364				'SELECT o.actionid,oc.scriptid'.
365				' FROM operations o,opcommand oc'.
366				' WHERE o.operationid=oc.operationid'.
367					' AND '.dbConditionInt('o.actionid', $actionIds).
368					' AND oc.type='.ZBX_SCRIPT_TYPE_GLOBAL_SCRIPT
369			);
370			while ($script = DBfetch($dbScripts)) {
371				if (!isset($scripts[$script['scriptid']])) {
372					$scripts[$script['scriptid']] = [];
373				}
374				$scripts[$script['scriptid']][$script['actionid']] = $script['actionid'];
375				$scriptIds[$script['scriptid']] = $script['scriptid'];
376			}
377
378			$allowedScripts = API::Script()->get([
379				'scriptids' => $scriptIds,
380				'output' => ['scriptid'],
381				'preservekeys' => true
382			]);
383			foreach ($scriptIds as $scriptId) {
384				if (isset($allowedScripts[$scriptId])) {
385					continue;
386				}
387				foreach ($scripts[$scriptId] as $actionId) {
388					unset($result[$actionId], $actionIds[$actionId]);
389				}
390			}
391			unset($allowedScripts);
392
393			// check users
394			$users = [];
395			$userIds = [];
396			$dbUsers = DBselect(
397				'SELECT o.actionid,omu.userid'.
398				' FROM operations o,opmessage_usr omu'.
399				' WHERE o.operationid=omu.operationid'.
400					' AND '.dbConditionInt('o.actionid', $actionIds)
401			);
402			while ($user = DBfetch($dbUsers)) {
403				if (!isset($users[$user['userid']])) {
404					$users[$user['userid']] = [];
405				}
406				$users[$user['userid']][$user['actionid']] = $user['actionid'];
407				$userIds[$user['userid']] = $user['userid'];
408			}
409
410			$allowedUsers = API::User()->get([
411				'userids' => $userIds,
412				'output' => ['userid'],
413				'preservekeys' => true
414			]);
415			foreach ($userIds as $userId) {
416				if (isset($allowedUsers[$userId])) {
417					continue;
418				}
419				foreach ($users[$userId] as $actionId) {
420					unset($result[$actionId], $actionIds[$actionId]);
421				}
422			}
423
424			// check usergroups
425			$userGroups = [];
426			$userGroupIds = [];
427			$dbUserGroups = DBselect(
428				'SELECT o.actionid,omg.usrgrpid'.
429				' FROM operations o,opmessage_grp omg'.
430				' WHERE o.operationid=omg.operationid'.
431					' AND '.dbConditionInt('o.actionid', $actionIds)
432			);
433			while ($userGroup = DBfetch($dbUserGroups)) {
434				if (!isset($userGroups[$userGroup['usrgrpid']])) {
435					$userGroups[$userGroup['usrgrpid']] = [];
436				}
437				$userGroups[$userGroup['usrgrpid']][$userGroup['actionid']] = $userGroup['actionid'];
438				$userGroupIds[$userGroup['usrgrpid']] = $userGroup['usrgrpid'];
439			}
440
441			$allowedUserGroups = API::UserGroup()->get([
442				'usrgrpids' => $userGroupIds,
443				'output' => ['usrgrpid'],
444				'preservekeys' => true
445			]);
446
447			foreach ($userGroupIds as $userGroupId) {
448				if (isset($allowedUserGroups[$userGroupId])) {
449					continue;
450				}
451				foreach ($userGroups[$userGroupId] as $actionId) {
452					unset($result[$actionId], $actionIds[$actionId]);
453				}
454			}
455		}
456
457		if (!is_null($options['countOutput'])) {
458			return $result;
459		}
460
461		if ($result) {
462			$result = $this->addRelatedObjects($options, $result);
463
464			foreach ($result as &$action) {
465				// unset the fields that are returned in the filter
466				unset($action['formula'], $action['evaltype']);
467
468				if ($options['selectFilter'] !== null) {
469					$filter = $this->unsetExtraFields(
470						[$action['filter']],
471						['conditions', 'formula', 'evaltype'],
472						$options['selectFilter']
473					);
474					$filter = reset($filter);
475
476					if (isset($filter['conditions'])) {
477						foreach ($filter['conditions'] as &$condition) {
478							unset($condition['actionid'], $condition['conditionid']);
479						}
480						unset($condition);
481					}
482
483					$action['filter'] = $filter;
484				}
485			}
486			unset($action);
487		}
488
489		// removing keys (hash -> array)
490		if (is_null($options['preservekeys'])) {
491			$result = zbx_cleanHashes($result);
492		}
493
494		return $result;
495	}
496
497	/**
498	 * Add actions
499	 *
500	 * @param array $actions multidimensional array with actions data
501	 * @param array $actions[0,...]['expression']
502	 * @param array $actions[0,...]['description']
503	 * @param array $actions[0,...]['type'] OPTIONAL
504	 * @param array $actions[0,...]['priority'] OPTIONAL
505	 * @param array $actions[0,...]['status'] OPTIONAL
506	 * @param array $actions[0,...]['comments'] OPTIONAL
507	 * @param array $actions[0,...]['url'] OPTIONAL
508	 * @param array $actions[0,...]['filter'] OPTIONAL
509	 * @return boolean
510	 */
511	public function create($actions) {
512		$actions = zbx_toArray($actions);
513
514		$this->validateCreate($actions);
515
516		// Set "evaltype" if specified in "filter" section of action.
517		foreach ($actions as &$action) {
518			if (isset($action['filter'])) {
519				$action['evaltype'] = $action['filter']['evaltype'];
520			}
521		}
522		unset($action);
523
524		// Insert actions into db, get back array with new actionids.
525		$actions = DB::save('actions', $actions);
526		$actions = zbx_toHash($actions, 'actionid');
527
528		$conditionsToCreate = [];
529		$operationsToCreate = [];
530		// Collect conditions and operations to be created and set appropriate action ID.
531		foreach ($actions as $actionId => &$action) {
532			if (isset($action['filter'])) {
533				foreach ($action['filter']['conditions'] as $condition) {
534					$condition['actionid'] = $actionId;
535					$conditionsToCreate[] = $condition;
536				}
537			}
538
539			foreach ($action['operations'] as $operation) {
540				$operation['actionid'] = $actionId;
541				$operationsToCreate[] = $operation;
542			}
543		}
544		unset($action);
545
546		$createdConditions = $this->addConditions($conditionsToCreate);
547
548		// Group back created action conditions by action ID to be used for updating action formula.
549		$conditionsForActions = [];
550		foreach ($createdConditions as $condition) {
551			$conditionsForActions[$condition['actionid']][$condition['conditionid']] = $condition;
552		}
553
554		// Update "formula" field if evaltype is custom expression.
555		foreach ($actions as $actionId => $action) {
556			if (isset($action['filter'])) {
557				$actionFilter = $action['filter'];
558				if ($actionFilter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
559					$this->updateFormula($actionId, $actionFilter['formula'], $conditionsForActions[$actionId]);
560				}
561			}
562		}
563
564		// Add operations.
565		$this->addOperations($operationsToCreate);
566
567		return ['actionids' => array_keys($actions)];
568	}
569
570	/**
571	 * Update actions
572	 *
573	 * @param array $actions multidimensional array with actions data
574	 * @param array $actions[0,...]['actionid']
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	 * @return boolean
584	 */
585	public function update($actions) {
586		$actions = zbx_toArray($actions);
587		$actions = zbx_toHash($actions, 'actionid');
588		$actionIds = array_keys($actions);
589
590		$actionsDb = $this->get([
591			'actionids'        => $actionIds,
592			'editable'         => true,
593			'output'           => API_OUTPUT_EXTEND,
594			'preservekeys'     => true,
595			'selectOperations' => API_OUTPUT_EXTEND,
596			'selectFilter'     => ['formula', 'conditions']
597		]);
598
599		$this->validateUpdate($actions, $actionsDb);
600
601		$operationsToCreate = [];
602		$operationsToUpdate = [];
603		$operationIdsForDelete = [];
604
605		$actionsUpdateData = [];
606
607		$newActionConditions = null;
608		foreach ($actions as $actionId => $action) {
609			$actionDb = $actionsDb[$actionId];
610
611			$actionUpdateValues = $action;
612			unset(
613				$actionUpdateValues['actionid'],
614				$actionUpdateValues['filter'],
615				$actionUpdateValues['operations'],
616				$actionUpdateValues['conditions'],
617				$actionUpdateValues['formula'],
618				$actionUpdateValues['evaltype']
619			);
620
621			if (isset($action['filter'])) {
622				$actionFilter = $action['filter'];
623
624				// set formula to empty string of not custom expression
625				if ($actionFilter['evaltype'] != CONDITION_EVAL_TYPE_EXPRESSION) {
626					$actionUpdateValues['formula'] = '';
627				}
628
629				$actionUpdateValues['evaltype'] = $actionFilter['evaltype'];
630			}
631
632			if (isset($action['operations'])) {
633				$operationsDb = $actionDb['operations'];
634				$operationsDb = zbx_toHash($operationsDb, 'operationid');
635
636				foreach ($action['operations'] as $operation) {
637					if (!isset($operation['operationid'])) {
638						$operation['actionid'] = $action['actionid'];
639						$operationsToCreate[] = $operation;
640					}
641					else {
642						$operationId = $operation['operationid'];
643
644						if (isset($operationsDb[$operationId])) {
645							$operationsToUpdate[] = $operation;
646							unset($operationsDb[$operationId]);
647						}
648					}
649				}
650				$operationIdsForDelete = array_merge($operationIdsForDelete, array_keys($operationsDb));
651			}
652
653			$actionsUpdateData[] = ['values' => $actionUpdateValues, 'where' => ['actionid' => $actionId]];
654		}
655
656		if ($actionsUpdateData) {
657			DB::update('actions', $actionsUpdateData);
658		}
659
660		// add, update and delete operations
661		$this->addOperations($operationsToCreate);
662		$this->updateOperations($operationsToUpdate, $actionsDb);
663		if (!empty($operationIdsForDelete)) {
664			$this->deleteOperations($operationIdsForDelete);
665		}
666
667		// set actionid for all conditions and group by actionid into $newActionConditions
668		$newActionConditions = null;
669		foreach ($actions as $actionId => $action) {
670			if (isset($action['filter'])) {
671				if ($newActionConditions === null) {
672					$newActionConditions = [];
673				}
674
675				$newActionConditions[$actionId] = [];
676				foreach ($action['filter']['conditions'] as $condition) {
677					$condition['actionid'] = $actionId;
678					$newActionConditions[$actionId][] = $condition;
679				}
680			}
681		}
682
683		// if we have any conditions, fetch current conditions from db and do replace by position and group result
684		// by actionid into $actionConditions
685		$actionConditions = [];
686		if ($newActionConditions !== null) {
687			$existingConditions = DBfetchArray(DBselect(
688				'SELECT conditionid,actionid,conditiontype,operator,value'.
689				' FROM conditions'.
690				' WHERE '.dbConditionInt('actionid', $actionIds).
691				' ORDER BY conditionid'
692			));
693			$existingActionConditions = [];
694			foreach ($existingConditions as $condition) {
695				$existingActionConditions[$condition['actionid']][] = $condition;
696			}
697
698			$conditions = DB::replaceByPosition('conditions', $existingActionConditions, $newActionConditions);
699			foreach ($conditions as $condition) {
700				$actionConditions[$condition['actionid']][] = $condition;
701			}
702		}
703
704		// update formulas for user expressions using new conditions
705		foreach ($actions as $actionId => $action) {
706			if (isset($action['filter']) && $action['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
707				$this->updateFormula($actionId, $action['filter']['formula'], $actionConditions[$actionId]);
708			}
709		}
710
711		return ['actionids' => $actionIds];
712	}
713
714	/**
715	 * @param array $conditions
716	 *
717	 * @return mixed
718	 */
719	protected function addConditions($conditions) {
720		foreach ($conditions as $condition) {
721			$connectionDbFields = [
722				'actionid' => null,
723				'conditiontype' => null
724			];
725			if (!check_db_fields($connectionDbFields, $condition)) {
726				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect parameters for condition.'));
727			}
728		}
729
730		return DB::save('conditions', $conditions);
731	}
732
733	protected function updateConditions($conditions) {
734		$update = [];
735		foreach ($conditions as $condition) {
736			$conditionId = $condition['conditionid'];
737			unset($condition['conditionid']);
738			$update = [
739				'values' => $condition,
740				'where' => ['conditionid' => $conditionId]
741			];
742		}
743		DB::update('conditions', $update);
744
745		return $conditions;
746	}
747
748	protected function deleteConditions($conditionids) {
749		DB::delete('conditions', ['conditionid' => $conditionids]);
750	}
751
752	/**
753	 * @param array $operations
754	 *
755	 * @return bool
756	 */
757	protected function addOperations($operations) {
758		foreach ($operations as $operation) {
759			$operationDbFields = [
760				'actionid' => null,
761				'operationtype' => null
762			];
763			if (!check_db_fields($operationDbFields, $operation)) {
764				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect parameter for operations.'));
765			}
766		}
767
768		$operations = DB::save('operations', $operations);
769		$operations = zbx_toHash($operations, 'operationid');
770
771		$opMessagesToInsert = [];
772		$opCommandsToInsert = [];
773		$opMessageGrpsToInsert = [];
774		$opMessageUsrsToInsert = [];
775		$opCommandHstsToInsert = [];
776		$opCommandGroupInserts = [];
777		$opGroupsToInsert = [];
778		$opTemplatesToInsert = [];
779		$opConditionsToInsert = [];
780		$opInventoryToInsert = [];
781
782		foreach ($operations as $operationId => $operation) {
783			switch ($operation['operationtype']) {
784				case OPERATION_TYPE_MESSAGE:
785					if (isset($operation['opmessage']) && !empty($operation['opmessage'])) {
786						$operation['opmessage']['operationid'] = $operationId;
787						$opMessagesToInsert[] = $operation['opmessage'];
788					}
789					if (isset($operation['opmessage_usr'])) {
790						foreach ($operation['opmessage_usr'] as $user) {
791							$opMessageUsrsToInsert[] = [
792								'operationid' => $operationId,
793								'userid' => $user['userid']
794							];
795						}
796					}
797					if (isset($operation['opmessage_grp'])) {
798						foreach ($operation['opmessage_grp'] as $userGroup) {
799							$opMessageGrpsToInsert[] = [
800								'operationid' => $operationId,
801								'usrgrpid' => $userGroup['usrgrpid']
802							];
803						}
804					}
805					break;
806				case OPERATION_TYPE_COMMAND:
807					if (isset($operation['opcommand']) && !empty($operation['opcommand'])) {
808						$operation['opcommand']['operationid'] = $operationId;
809						$opCommandsToInsert[] = $operation['opcommand'];
810					}
811					if (isset($operation['opcommand_hst'])) {
812						foreach ($operation['opcommand_hst'] as $host) {
813							$opCommandHstsToInsert[] = [
814								'operationid' => $operationId,
815								'hostid' => $host['hostid']
816							];
817						}
818					}
819					if (isset($operation['opcommand_grp'])) {
820						foreach ($operation['opcommand_grp'] as $hostGroup) {
821							$opCommandGroupInserts[] = [
822								'operationid' => $operationId,
823								'groupid' => $hostGroup['groupid']
824							];
825						}
826					}
827					break;
828				case OPERATION_TYPE_GROUP_ADD:
829				case OPERATION_TYPE_GROUP_REMOVE:
830					foreach ($operation['opgroup'] as $hostGroup) {
831						$opGroupsToInsert[] = [
832							'operationid' => $operationId,
833							'groupid' => $hostGroup['groupid']
834						];
835					}
836					break;
837				case OPERATION_TYPE_TEMPLATE_ADD:
838				case OPERATION_TYPE_TEMPLATE_REMOVE:
839					foreach ($operation['optemplate'] as $template) {
840						$opTemplatesToInsert[] = [
841							'operationid' => $operationId,
842							'templateid' => $template['templateid']
843						];
844					}
845					break;
846				case OPERATION_TYPE_HOST_ADD:
847				case OPERATION_TYPE_HOST_REMOVE:
848				case OPERATION_TYPE_HOST_ENABLE:
849				case OPERATION_TYPE_HOST_DISABLE:
850					break;
851				case OPERATION_TYPE_HOST_INVENTORY:
852					$opInventoryToInsert[] = [
853						'operationid' => $operationId,
854						'inventory_mode' => $operation['opinventory']['inventory_mode']
855					];
856					break;
857			}
858			if (isset($operation['opconditions'])) {
859				foreach ($operation['opconditions'] as $opCondition) {
860					$opCondition['operationid'] = $operationId;
861					$opConditionsToInsert[] = $opCondition;
862				}
863			}
864		}
865		DB::insert('opconditions', $opConditionsToInsert);
866		DB::insert('opmessage', $opMessagesToInsert, false);
867		DB::insert('opcommand', $opCommandsToInsert, false);
868		DB::insert('opmessage_grp', $opMessageGrpsToInsert);
869		DB::insert('opmessage_usr', $opMessageUsrsToInsert);
870		DB::insert('opcommand_hst', $opCommandHstsToInsert);
871		DB::insert('opcommand_grp', $opCommandGroupInserts);
872		DB::insert('opgroup', $opGroupsToInsert);
873		DB::insert('optemplate', $opTemplatesToInsert);
874		DB::insert('opinventory', $opInventoryToInsert, false);
875
876		return true;
877	}
878
879	/**
880	 * @param array $operations
881	 * @param array $actionsDb
882	 */
883	protected function updateOperations($operations, $actionsDb) {
884		$operationsUpdate = [];
885
886		// messages
887		$opMessagesToInsert = [];
888		$opMessagesToUpdate = [];
889		$opMessagesToDeleteByOpId = [];
890
891		$opMessageGrpsToInsert = [];
892		$opMessageUsrsToInsert = [];
893		$opMessageGrpsToDeleteByOpId = [];
894		$opMessageUsrsToDeleteByOpId = [];
895
896		// commands
897		$opCommandsToInsert = [];
898		$opCommandsToUpdate = [];
899		$opCommandsToDeleteByOpId = [];
900
901		$opCommandGrpsToInsert = [];
902		$opCommandHstsToInsert = [];
903		$opCommandGrpsToDeleteByOpId = [];
904		$opCommandHstsToDeleteByOpId = [];
905
906		// groups
907		$opGroupsToInsert = [];
908		$opGroupsToDeleteByOpId = [];
909
910		// templates
911		$opTemplateToInsert = [];
912		$opTemplatesToDeleteByOpId = [];
913
914		// operation conditions
915		$opConditionsToInsert = [];
916
917		// inventory
918		$opInventoryToInsert = [];
919		$opInventoryToUpdate = [];
920		$opInventoryToDeleteByOpId = [];
921
922		foreach ($operations as $operation) {
923			$operationsDb = zbx_toHash($actionsDb[$operation['actionid']]['operations'], 'operationid');
924			$operationDb = $operationsDb[$operation['operationid']];
925
926			$typeChanged = false;
927			if (isset($operation['operationtype']) && ($operation['operationtype'] != $operationDb['operationtype'])) {
928				$typeChanged = true;
929
930				switch ($operationDb['operationtype']) {
931					case OPERATION_TYPE_MESSAGE:
932						$opMessagesToDeleteByOpId[] = $operationDb['operationid'];
933						$opMessageGrpsToDeleteByOpId[] = $operationDb['operationid'];
934						$opMessageUsrsToDeleteByOpId[] = $operationDb['operationid'];
935						break;
936					case OPERATION_TYPE_COMMAND:
937						$opCommandsToDeleteByOpId[] = $operationDb['operationid'];
938						$opCommandHstsToDeleteByOpId[] = $operationDb['operationid'];
939						$opCommandGrpsToDeleteByOpId[] = $operationDb['operationid'];
940						break;
941					case OPERATION_TYPE_GROUP_ADD:
942						if ($operation['operationtype'] == OPERATION_TYPE_GROUP_REMOVE) {
943							break;
944						}
945					case OPERATION_TYPE_GROUP_REMOVE:
946						if ($operation['operationtype'] == OPERATION_TYPE_GROUP_ADD) {
947							break;
948						}
949						$opGroupsToDeleteByOpId[] = $operationDb['operationid'];
950						break;
951					case OPERATION_TYPE_TEMPLATE_ADD:
952						if ($operation['operationtype'] == OPERATION_TYPE_TEMPLATE_REMOVE) {
953							break;
954						}
955					case OPERATION_TYPE_TEMPLATE_REMOVE:
956						if ($operation['operationtype'] == OPERATION_TYPE_TEMPLATE_ADD) {
957							break;
958						}
959						$opTemplatesToDeleteByOpId[] = $operationDb['operationid'];
960						break;
961					case OPERATION_TYPE_HOST_INVENTORY:
962						$opInventoryToDeleteByOpId[] = $operationDb['operationid'];
963						break;
964				}
965			}
966
967			if (!isset($operation['operationtype'])) {
968				$operation['operationtype'] = $operationDb['operationtype'];
969			}
970
971			switch ($operation['operationtype']) {
972				case OPERATION_TYPE_MESSAGE:
973					if (!isset($operation['opmessage_grp'])) {
974						$operation['opmessage_grp'] = [];
975					}
976					else {
977						zbx_array_push($operation['opmessage_grp'], ['operationid' => $operation['operationid']]);
978					}
979
980					if (!isset($operation['opmessage_usr'])) {
981						$operation['opmessage_usr'] = [];
982					}
983					else {
984						zbx_array_push($operation['opmessage_usr'], ['operationid' => $operation['operationid']]);
985					}
986
987					if (!isset($operationDb['opmessage_usr'])) {
988						$operationDb['opmessage_usr'] = [];
989					}
990					if (!isset($operationDb['opmessage_grp'])) {
991						$operationDb['opmessage_grp'] = [];
992					}
993
994					if ($typeChanged) {
995						$operation['opmessage']['operationid'] = $operation['operationid'];
996						$opMessagesToInsert[] = $operation['opmessage'];
997
998						$opMessageGrpsToInsert = array_merge($opMessageGrpsToInsert, $operation['opmessage_grp']);
999						$opMessageUsrsToInsert = array_merge($opMessageUsrsToInsert, $operation['opmessage_usr']);
1000					}
1001					else {
1002						$opMessagesToUpdate[] = [
1003							'values' => $operation['opmessage'],
1004							'where' => ['operationid'=>$operation['operationid']]
1005						];
1006
1007						$diff = zbx_array_diff($operation['opmessage_grp'], $operationDb['opmessage_grp'], 'usrgrpid');
1008						$opMessageGrpsToInsert = array_merge($opMessageGrpsToInsert, $diff['first']);
1009
1010						foreach ($diff['second'] as $opMessageGrp) {
1011							DB::delete('opmessage_grp', [
1012								'usrgrpid' => $opMessageGrp['usrgrpid'],
1013								'operationid' => $operation['operationid']
1014							]);
1015						}
1016
1017						$diff = zbx_array_diff($operation['opmessage_usr'], $operationDb['opmessage_usr'], 'userid');
1018						$opMessageUsrsToInsert = array_merge($opMessageUsrsToInsert, $diff['first']);
1019						foreach ($diff['second'] as $opMessageUsr) {
1020							DB::delete('opmessage_usr', [
1021								'userid' => $opMessageUsr['userid'],
1022								'operationid' => $operation['operationid']
1023							]);
1024						}
1025					}
1026					break;
1027				case OPERATION_TYPE_COMMAND:
1028					if (!isset($operation['opcommand_grp'])) {
1029						$operation['opcommand_grp'] = [];
1030					}
1031					else {
1032						zbx_array_push($operation['opcommand_grp'], ['operationid' => $operation['operationid']]);
1033					}
1034
1035					if (!isset($operation['opcommand_hst'])) {
1036						$operation['opcommand_hst'] = [];
1037					}
1038					else {
1039						zbx_array_push($operation['opcommand_hst'], ['operationid' => $operation['operationid']]);
1040					}
1041
1042					if (!isset($operationDb['opcommand_grp'])) {
1043						$operationDb['opcommand_grp'] = [];
1044					}
1045					if (!isset($operationDb['opcommand_hst'])) {
1046						$operationDb['opcommand_hst'] = [];
1047					}
1048
1049					if ($typeChanged) {
1050						$operation['opcommand']['operationid'] = $operation['operationid'];
1051						$opCommandsToInsert[] = $operation['opcommand'];
1052
1053						$opCommandGrpsToInsert = array_merge($opCommandGrpsToInsert, $operation['opcommand_grp']);
1054						$opCommandHstsToInsert = array_merge($opCommandHstsToInsert, $operation['opcommand_hst']);
1055					}
1056					else {
1057						// clear and reset fields to default values on type change
1058						if ($operation['opcommand']['type'] == ZBX_SCRIPT_TYPE_GLOBAL_SCRIPT) {
1059							$operation['opcommand']['command'] = '';
1060						}
1061						else {
1062							$operation['opcommand']['scriptid'] = null;
1063						}
1064						if ($operation['opcommand']['type'] != ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT) {
1065							$operation['opcommand']['execute_on'] = ZBX_SCRIPT_EXECUTE_ON_AGENT;
1066						}
1067						if ($operation['opcommand']['type'] != ZBX_SCRIPT_TYPE_SSH
1068								&& $operation['opcommand']['type'] != ZBX_SCRIPT_TYPE_TELNET) {
1069							$operation['opcommand']['port'] = '';
1070							$operation['opcommand']['username'] = '';
1071							$operation['opcommand']['password'] = '';
1072						}
1073						if (!isset($operation['opcommand']['authtype'])) {
1074							$operation['opcommand']['authtype'] = ITEM_AUTHTYPE_PASSWORD;
1075						}
1076						if ($operation['opcommand']['authtype'] == ITEM_AUTHTYPE_PASSWORD) {
1077							$operation['opcommand']['publickey'] = '';
1078							$operation['opcommand']['privatekey'] = '';
1079						}
1080
1081						$opCommandsToUpdate[] = [
1082							'values' => $operation['opcommand'],
1083							'where' => ['operationid' => $operation['operationid']]
1084						];
1085
1086						$diff = zbx_array_diff($operation['opcommand_grp'], $operationDb['opcommand_grp'], 'groupid');
1087						$opCommandGrpsToInsert = array_merge($opCommandGrpsToInsert, $diff['first']);
1088
1089						foreach ($diff['second'] as $opMessageGrp) {
1090							DB::delete('opcommand_grp', [
1091								'groupid' => $opMessageGrp['groupid'],
1092								'operationid' => $operation['operationid']
1093							]);
1094						}
1095
1096						$diff = zbx_array_diff($operation['opcommand_hst'], $operationDb['opcommand_hst'], 'hostid');
1097						$opCommandHstsToInsert = array_merge($opCommandHstsToInsert, $diff['first']);
1098						$opCommandHostIds = zbx_objectValues($diff['second'], 'opcommand_hstid');
1099						if ($opCommandHostIds) {
1100							DB::delete('opcommand_hst', [
1101								'opcommand_hstid' => $opCommandHostIds
1102							]);
1103						}
1104					}
1105					break;
1106				case OPERATION_TYPE_GROUP_ADD:
1107				case OPERATION_TYPE_GROUP_REMOVE:
1108					if (!isset($operation['opgroup'])) {
1109						$operation['opgroup'] = [];
1110					}
1111					else {
1112						zbx_array_push($operation['opgroup'], ['operationid' => $operation['operationid']]);
1113					}
1114
1115					if (!isset($operationDb['opgroup'])) {
1116						$operationDb['opgroup'] = [];
1117					}
1118
1119					$diff = zbx_array_diff($operation['opgroup'], $operationDb['opgroup'], 'groupid');
1120					$opGroupsToInsert = array_merge($opGroupsToInsert, $diff['first']);
1121					foreach ($diff['second'] as $opGroup) {
1122						DB::delete('opgroup', [
1123							'groupid' => $opGroup['groupid'],
1124							'operationid' => $operation['operationid']
1125						]);
1126					}
1127					break;
1128				case OPERATION_TYPE_TEMPLATE_ADD:
1129				case OPERATION_TYPE_TEMPLATE_REMOVE:
1130					if (!isset($operation['optemplate'])) {
1131						$operation['optemplate'] = [];
1132					}
1133					else {
1134						zbx_array_push($operation['optemplate'], ['operationid' => $operation['operationid']]);
1135					}
1136
1137					if (!isset($operationDb['optemplate'])) {
1138						$operationDb['optemplate'] = [];
1139					}
1140
1141					$diff = zbx_array_diff($operation['optemplate'], $operationDb['optemplate'], 'templateid');
1142					$opTemplateToInsert = array_merge($opTemplateToInsert, $diff['first']);
1143
1144					foreach ($diff['second'] as $opTemplate) {
1145						DB::delete('optemplate', [
1146							'templateid' => $opTemplate['templateid'],
1147							'operationid' => $operation['operationid']
1148						]);
1149					}
1150					break;
1151				case OPERATION_TYPE_HOST_INVENTORY:
1152					if ($typeChanged) {
1153						$operation['opinventory']['operationid'] = $operation['operationid'];
1154						$opInventoryToInsert[] = $operation['opinventory'];
1155					}
1156					else {
1157						$opInventoryToUpdate[] = [
1158							'values' => $operation['opinventory'],
1159							'where' => ['operationid' => $operation['operationid']]
1160						];
1161					}
1162					break;
1163			}
1164
1165			if (!isset($operation['opconditions'])) {
1166				$operation['opconditions'] = [];
1167			}
1168			else {
1169				zbx_array_push($operation['opconditions'], ['operationid' => $operation['operationid']]);
1170			}
1171
1172			self::validateOperationConditions($operation['opconditions']);
1173
1174			$diff = zbx_array_diff($operation['opconditions'], $operationDb['opconditions'], 'opconditionid');
1175			$opConditionsToInsert = array_merge($opConditionsToInsert, $diff['first']);
1176
1177			$opConditionsIdsToDelete = zbx_objectValues($diff['second'], 'opconditionid');
1178			if (!empty($opConditionsIdsToDelete)) {
1179				DB::delete('opconditions', ['opconditionid' => $opConditionsIdsToDelete]);
1180			}
1181
1182			$operationId = $operation['operationid'];
1183			unset($operation['operationid']);
1184			if (!empty($operation)) {
1185				$operationsUpdate[] = [
1186					'values' => $operation,
1187					'where' => ['operationid' => $operationId]
1188				];
1189			}
1190		}
1191
1192		DB::update('operations', $operationsUpdate);
1193
1194		if (!empty($opMessagesToDeleteByOpId)) {
1195			DB::delete('opmessage', ['operationid' => $opMessagesToDeleteByOpId]);
1196		}
1197		if (!empty($opCommandsToDeleteByOpId)) {
1198			DB::delete('opcommand', ['operationid' => $opCommandsToDeleteByOpId]);
1199		}
1200		if (!empty($opMessageGrpsToDeleteByOpId)) {
1201			DB::delete('opmessage_grp', ['operationid' => $opMessageGrpsToDeleteByOpId]);
1202		}
1203		if (!empty($opMessageUsrsToDeleteByOpId)) {
1204			DB::delete('opmessage_usr', ['operationid' => $opMessageUsrsToDeleteByOpId]);
1205		}
1206		if (!empty($opCommandHstsToDeleteByOpId)) {
1207			DB::delete('opcommand_hst', ['operationid' => $opCommandHstsToDeleteByOpId]);
1208		}
1209		if (!empty($opCommandGrpsToDeleteByOpId)) {
1210			DB::delete('opcommand_grp', ['operationid' => $opCommandGrpsToDeleteByOpId]);
1211		}
1212		if (!empty($opCommandGrpsToDeleteByOpId)) {
1213			DB::delete('opcommand_grp', ['opcommand_grpid' => $opCommandGrpsToDeleteByOpId]);
1214		}
1215		if (!empty($opCommandHstsToDeleteByOpId)) {
1216			DB::delete('opcommand_hst', ['opcommand_hstid' => $opCommandHstsToDeleteByOpId]);
1217		}
1218		if (!empty($opGroupsToDeleteByOpId)) {
1219			DB::delete('opgroup', ['operationid' => $opGroupsToDeleteByOpId]);
1220		}
1221		if (!empty($opTemplatesToDeleteByOpId)) {
1222			DB::delete('optemplate', ['operationid' => $opTemplatesToDeleteByOpId]);
1223		}
1224		if (!empty($opInventoryToDeleteByOpId)) {
1225			DB::delete('opinventory', ['operationid' => $opInventoryToDeleteByOpId]);
1226		}
1227
1228		DB::insert('opmessage', $opMessagesToInsert, false);
1229		DB::insert('opcommand', $opCommandsToInsert, false);
1230		DB::insert('opmessage_grp', $opMessageGrpsToInsert);
1231		DB::insert('opmessage_usr', $opMessageUsrsToInsert);
1232		DB::insert('opcommand_grp', $opCommandGrpsToInsert);
1233		DB::insert('opcommand_hst', $opCommandHstsToInsert);
1234		DB::insert('opgroup', $opGroupsToInsert);
1235		DB::insert('optemplate', $opTemplateToInsert);
1236		DB::update('opmessage', $opMessagesToUpdate);
1237		DB::update('opcommand', $opCommandsToUpdate);
1238		DB::insert('opconditions', $opConditionsToInsert);
1239		DB::insert('opinventory', $opInventoryToInsert, false);
1240		DB::update('opinventory', $opInventoryToUpdate);
1241	}
1242
1243	protected function deleteOperations($operationIds) {
1244		DB::delete('operations', ['operationid' => $operationIds]);
1245	}
1246
1247	/**
1248	 * Delete actions.
1249	 *
1250	 * @param array $actionids
1251	 *
1252	 * @return array
1253	 */
1254	public function delete(array $actionids) {
1255		if (empty($actionids)) {
1256			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
1257		}
1258
1259		$delActions = $this->get([
1260			'actionids' => $actionids,
1261			'editable' => true,
1262			'output' => ['actionid'],
1263			'preservekeys' => true
1264		]);
1265		foreach ($actionids as $actionid) {
1266			if (isset($delActions[$actionid])) {
1267				continue;
1268			}
1269			self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
1270		}
1271
1272		DB::delete('actions', ['actionid' => $actionids]);
1273		DB::delete('alerts', ['actionid' => $actionids]);
1274
1275		return ['actionids' => $actionids];
1276	}
1277
1278	/**
1279	 * @param array $operations
1280	 *
1281	 * @return bool
1282	 */
1283	public function validateOperationsIntegrity($operations) {
1284		$operations = zbx_toArray($operations);
1285
1286		foreach ($operations as $operation) {
1287			if ((isset($operation['esc_step_from']) || isset($operation['esc_step_to'])) && !isset($operation['esc_step_from'], $operation['esc_step_to'])) {
1288				self::exception(ZBX_API_ERROR_PARAMETERS, _('esc_step_from and esc_step_to must be set together.'));
1289			}
1290
1291			if (isset($operation['esc_step_from'], $operation['esc_step_to'])) {
1292				if ($operation['esc_step_from'] < 1 || $operation['esc_step_to'] < 0) {
1293					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation escalation step values.'));
1294				}
1295
1296				if ($operation['esc_step_from'] > $operation['esc_step_to'] && $operation['esc_step_to'] != 0) {
1297					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation escalation step values.'));
1298				}
1299			}
1300
1301			if (isset($operation['esc_period'])) {
1302				if (isset($operation['esc_period']) && $operation['esc_period'] != 0 && $operation['esc_period'] < SEC_PER_MIN) {
1303					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation step duration.'));
1304				}
1305			}
1306
1307			$hostIdsAll = [];
1308			$hostGroupIdsAll = [];
1309			$userIdsAll = [];
1310			$userGroupIdsAll = [];
1311			switch ($operation['operationtype']) {
1312				case OPERATION_TYPE_MESSAGE:
1313					$userIds = isset($operation['opmessage_usr']) ? zbx_objectValues($operation['opmessage_usr'], 'userid') : [];
1314					$userGroupIds = isset($operation['opmessage_grp']) ? zbx_objectValues($operation['opmessage_grp'], 'usrgrpid') : [];
1315
1316					if (empty($userIds) && empty($userGroupIds)) {
1317						self::exception(ZBX_API_ERROR_PARAMETERS, _('No recipients for action operation message.'));
1318					}
1319
1320					$userIdsAll = array_merge($userIdsAll, $userIds);
1321					$userGroupIdsAll = array_merge($userGroupIdsAll, $userGroupIds);
1322					break;
1323				case OPERATION_TYPE_COMMAND:
1324					if (!isset($operation['opcommand']['type'])) {
1325						self::exception(ZBX_API_ERROR_PARAMETERS, _('No command type specified for action operation.'));
1326					}
1327
1328					if ((!isset($operation['opcommand']['command']) || zbx_empty(trim($operation['opcommand']['command'])))
1329							&& $operation['opcommand']['type'] != ZBX_SCRIPT_TYPE_GLOBAL_SCRIPT) {
1330						self::exception(ZBX_API_ERROR_PARAMETERS, _s('No command specified for action operation.'));
1331					}
1332
1333					switch ($operation['opcommand']['type']) {
1334						case ZBX_SCRIPT_TYPE_IPMI:
1335							break;
1336						case ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT:
1337							if (!isset($operation['opcommand']['execute_on'])) {
1338								self::exception(ZBX_API_ERROR_PARAMETERS, _s('No execution target specified for action operation command "%s".', $operation['opcommand']['command']));
1339							}
1340							break;
1341						case ZBX_SCRIPT_TYPE_SSH:
1342							if (!isset($operation['opcommand']['authtype']) || zbx_empty($operation['opcommand']['authtype'])) {
1343								self::exception(ZBX_API_ERROR_PARAMETERS, _s('No authentication type specified for action operation command "%s".', $operation['opcommand']['command']));
1344							}
1345
1346							if (!isset($operation['opcommand']['username']) || zbx_empty($operation['opcommand']['username'])) {
1347								self::exception(ZBX_API_ERROR_PARAMETERS, _s('No authentication user name specified for action operation command "%s".', $operation['opcommand']['command']));
1348							}
1349
1350							if ($operation['opcommand']['authtype'] == ITEM_AUTHTYPE_PUBLICKEY) {
1351								if (!isset($operation['opcommand']['publickey']) || zbx_empty($operation['opcommand']['publickey'])) {
1352									self::exception(ZBX_API_ERROR_PARAMETERS, _s('No public key file specified for action operation command "%s".', $operation['opcommand']['command']));
1353								}
1354								if (!isset($operation['opcommand']['privatekey']) || zbx_empty($operation['opcommand']['privatekey'])) {
1355									self::exception(ZBX_API_ERROR_PARAMETERS, _s('No private key file specified for action operation command "%s".', $operation['opcommand']['command']));
1356								}
1357							}
1358							break;
1359						case ZBX_SCRIPT_TYPE_TELNET:
1360							if (!isset($operation['opcommand']['username']) || zbx_empty($operation['opcommand']['username'])) {
1361								self::exception(ZBX_API_ERROR_PARAMETERS, _s('No authentication user name specified for action operation command "%s".', $operation['opcommand']['command']));
1362							}
1363							break;
1364						case ZBX_SCRIPT_TYPE_GLOBAL_SCRIPT:
1365							if (!isset($operation['opcommand']['scriptid']) || zbx_empty($operation['opcommand']['scriptid'])) {
1366								self::exception(ZBX_API_ERROR_PARAMETERS, _('No script specified for action operation command.'));
1367							}
1368							$scripts = API::Script()->get([
1369								'output' => ['scriptid','name'],
1370								'scriptids' => $operation['opcommand']['scriptid'],
1371								'preservekeys' => true
1372							]);
1373							if (!isset($scripts[$operation['opcommand']['scriptid']])) {
1374								self::exception(ZBX_API_ERROR_PARAMETERS, _('Specified script does not exist or you do not have rights on it for action operation command.'));
1375							}
1376							break;
1377						default:
1378							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation command type.'));
1379					}
1380
1381					if (isset($operation['opcommand']['port']) && !zbx_empty($operation['opcommand']['port'])) {
1382						if (zbx_ctype_digit($operation['opcommand']['port'])) {
1383							if ($operation['opcommand']['port'] > 65535 || $operation['opcommand']['port'] < 1) {
1384								self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect action operation port "%s".', $operation['opcommand']['port']));
1385							}
1386						}
1387						else {
1388							$user_macro_parser = new CUserMacroParser();
1389
1390							if ($user_macro_parser->parse($operation['opcommand']['port']) != CParser::PARSE_SUCCESS) {
1391								self::exception(ZBX_API_ERROR_PARAMETERS,
1392									_s('Incorrect action operation port "%s".', $operation['opcommand']['port'])
1393								);
1394							}
1395						}
1396					}
1397
1398					$groupIds = [];
1399					if (isset($operation['opcommand_grp'])) {
1400						$groupIds = zbx_objectValues($operation['opcommand_grp'], 'groupid');
1401					}
1402
1403					$hostIds = [];
1404					$withoutCurrent = true;
1405					if (isset($operation['opcommand_hst'])) {
1406						foreach ($operation['opcommand_hst'] as $hstCommand) {
1407							if ($hstCommand['hostid'] == 0) {
1408								$withoutCurrent = false;
1409							}
1410							else {
1411								$hostIds[$hstCommand['hostid']] = $hstCommand['hostid'];
1412							}
1413						}
1414					}
1415
1416					if (empty($groupIds) && empty($hostIds) && $withoutCurrent) {
1417						if ($operation['opcommand']['type'] == ZBX_SCRIPT_TYPE_GLOBAL_SCRIPT) {
1418							self::exception(ZBX_API_ERROR_PARAMETERS, _s('You did not specify targets for action operation global script "%s".', $scripts[$operation['opcommand']['scriptid']]['name']));
1419						}
1420						else {
1421							self::exception(ZBX_API_ERROR_PARAMETERS, _s('You did not specify targets for action operation command "%s".', $operation['opcommand']['command']));
1422						}
1423					}
1424
1425					$hostIdsAll = array_merge($hostIdsAll, $hostIds);
1426					$hostGroupIdsAll = array_merge($hostGroupIdsAll, $groupIds);
1427					break;
1428				case OPERATION_TYPE_GROUP_ADD:
1429				case OPERATION_TYPE_GROUP_REMOVE:
1430					$groupIds = isset($operation['opgroup']) ? zbx_objectValues($operation['opgroup'], 'groupid') : [];
1431					if (empty($groupIds)) {
1432						self::exception(ZBX_API_ERROR_PARAMETERS, _('Operation has no group to operate.'));
1433					}
1434					$hostGroupIdsAll = array_merge($hostGroupIdsAll, $groupIds);
1435					break;
1436				case OPERATION_TYPE_TEMPLATE_ADD:
1437				case OPERATION_TYPE_TEMPLATE_REMOVE:
1438					$templateIds = isset($operation['optemplate']) ? zbx_objectValues($operation['optemplate'], 'templateid') : [];
1439					if (empty($templateIds)) {
1440						self::exception(ZBX_API_ERROR_PARAMETERS, _('Operation has no template to operate.'));
1441					}
1442					$hostIdsAll = array_merge($hostIdsAll, $templateIds);
1443					break;
1444				case OPERATION_TYPE_HOST_ADD:
1445				case OPERATION_TYPE_HOST_REMOVE:
1446				case OPERATION_TYPE_HOST_ENABLE:
1447				case OPERATION_TYPE_HOST_DISABLE:
1448					break;
1449
1450				case OPERATION_TYPE_HOST_INVENTORY:
1451					if (!array_key_exists('opinventory', $operation)
1452							|| !array_key_exists('inventory_mode', $operation['opinventory'])) {
1453						self::exception(ZBX_API_ERROR_PARAMETERS,
1454							_('No inventory mode specified for action operation.')
1455						);
1456					}
1457					if ($operation['opinventory']['inventory_mode'] != HOST_INVENTORY_MANUAL
1458							&& $operation['opinventory']['inventory_mode'] != HOST_INVENTORY_AUTOMATIC) {
1459						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect inventory mode in action operation.'));
1460					}
1461					break;
1462
1463				default:
1464					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation type.'));
1465			}
1466		}
1467
1468		if (!API::HostGroup()->isWritable($hostGroupIdsAll)) {
1469			self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation host group. Host group does not exist or you have no access to this host group.'));
1470		}
1471		if (!API::Host()->isWritable($hostIdsAll)) {
1472			self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation host. Host does not exist or you have no access to this host.'));
1473		}
1474		if (!API::User()->isReadable($userIdsAll)) {
1475			self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation user. User does not exist or you have no access to this user.'));
1476		}
1477		if (!API::UserGroup()->isReadable($userGroupIdsAll)) {
1478			self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation user group. User group does not exist or you have no access to this user group.'));
1479		}
1480
1481		return true;
1482	}
1483
1484	/**
1485	 * Validate operation conditions.
1486	 *
1487	 * @static
1488	 * @param $conditions
1489	 * @return bool
1490	 */
1491	public static function validateOperationConditions($conditions) {
1492		$conditions = zbx_toArray($conditions);
1493		$ackStatuses = [
1494			EVENT_ACKNOWLEDGED => 1,
1495			EVENT_NOT_ACKNOWLEDGED => 1
1496		];
1497
1498		foreach ($conditions as $condition) {
1499			switch ($condition['conditiontype']) {
1500				case CONDITION_TYPE_EVENT_ACKNOWLEDGED:
1501					if (!isset($ackStatuses[$condition['value']])) {
1502						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation condition acknowledge type.'));
1503					}
1504					break;
1505
1506				default:
1507					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operation condition type.'));
1508					break;
1509			}
1510		}
1511
1512		return true;
1513	}
1514
1515	protected function addRelatedObjects(array $options, array $result) {
1516		$result = parent::addRelatedObjects($options, $result);
1517
1518		$actionIds = array_keys($result);
1519
1520		// adding formulas
1521		if ($options['selectFilter'] !== null) {
1522			$formulaRequested = $this->outputIsRequested('formula', $options['selectFilter']);
1523			$evalFormulaRequested = $this->outputIsRequested('eval_formula', $options['selectFilter']);
1524			$conditionsRequested = $this->outputIsRequested('conditions', $options['selectFilter']);
1525
1526			$filters = [];
1527			foreach ($result as $action) {
1528				$filters[$action['actionid']] = [
1529					'evaltype' => $action['evaltype'],
1530					'formula' => isset($action['formula']) ? $action['formula'] : ''
1531				];
1532			}
1533
1534			if ($formulaRequested || $evalFormulaRequested || $conditionsRequested) {
1535				$conditions = API::getApiService()->select('conditions', [
1536						'output' => ['actionid', 'conditionid', 'conditiontype', 'operator', 'value'],
1537						'filter' => ['actionid' => $actionIds],
1538					'preservekeys' => true
1539				]);
1540
1541				$relationMap = $this->createRelationMap($conditions, 'actionid', 'conditionid');
1542				$filters = $relationMap->mapMany($filters, $conditions, 'conditions');
1543
1544				foreach ($filters as &$filter) {
1545					// in case of a custom expression - use the given formula
1546					if ($filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
1547						$formula = $filter['formula'];
1548					}
1549					// in other cases - generate the formula automatically
1550					else {
1551						$conditions = $filter['conditions'];
1552
1553						// sort conditions
1554						$sortFields = [
1555							['field' => 'conditiontype', 'order' => ZBX_SORT_DOWN],
1556							['field' => 'operator', 'order' => ZBX_SORT_DOWN],
1557							['field' => 'value', 'order' => ZBX_SORT_DOWN]
1558						];
1559						CArrayHelper::sort($conditions, $sortFields);
1560
1561						$conditionsForFormula = [];
1562						foreach ($conditions as $condition) {
1563							$conditionsForFormula[$condition['conditionid']] = $condition['conditiontype'];
1564						}
1565						$formula = CConditionHelper::getFormula($conditionsForFormula, $filter['evaltype']);
1566					}
1567
1568					// generate formulaids from the effective formula
1569					$formulaIds = CConditionHelper::getFormulaIds($formula);
1570					foreach ($filter['conditions'] as &$condition) {
1571						$condition['formulaid'] = $formulaIds[$condition['conditionid']];
1572					}
1573					unset($condition);
1574
1575					// generated a letter based formula only for actions with custom expressions
1576					if ($formulaRequested && $filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
1577						$filter['formula'] = CConditionHelper::replaceNumericIds($formula, $formulaIds);
1578					}
1579
1580					if ($evalFormulaRequested) {
1581						$filter['eval_formula'] = CConditionHelper::replaceNumericIds($formula, $formulaIds);
1582					}
1583				}
1584				unset($filter);
1585			}
1586
1587			// add filters to the result
1588			foreach ($result as &$action) {
1589				$action['filter'] = $filters[$action['actionid']];
1590			}
1591			unset($action);
1592		}
1593
1594		// adding operations
1595		if ($options['selectOperations'] !== null && $options['selectOperations'] != API_OUTPUT_COUNT) {
1596			$operations = API::getApiService()->select('operations', [
1597				'output' => $this->outputExtend($options['selectOperations'],
1598					['operationid', 'actionid', 'operationtype']
1599				),
1600				'filter' => ['actionid' => $actionIds],
1601				'preservekeys' => true
1602			]);
1603			$relationMap = $this->createRelationMap($operations, 'actionid', 'operationid');
1604			$operationIds = $relationMap->getRelatedIds();
1605
1606			if ($this->outputIsRequested('opconditions', $options['selectOperations'])) {
1607				foreach ($operations as &$operation) {
1608					$operation['opconditions'] = [];
1609				}
1610				unset($operation);
1611
1612				$res = DBselect('SELECT op.* FROM opconditions op WHERE '.dbConditionInt('op.operationid', $operationIds));
1613				while ($opcondition = DBfetch($res)) {
1614					$operations[$opcondition['operationid']]['opconditions'][] = $opcondition;
1615				}
1616			}
1617
1618			$opmessage = [];
1619			$opcommand = [];
1620			$opgroup = [];
1621			$optemplate = [];
1622			$opinventory = [];
1623
1624			foreach ($operations as $operationid => $operation) {
1625				switch ($operation['operationtype']) {
1626					case OPERATION_TYPE_MESSAGE:
1627						$opmessage[] = $operationid;
1628						break;
1629					case OPERATION_TYPE_COMMAND:
1630						$opcommand[] = $operationid;
1631						break;
1632					case OPERATION_TYPE_GROUP_ADD:
1633					case OPERATION_TYPE_GROUP_REMOVE:
1634						$opgroup[] = $operationid;
1635						break;
1636					case OPERATION_TYPE_TEMPLATE_ADD:
1637					case OPERATION_TYPE_TEMPLATE_REMOVE:
1638						$optemplate[] = $operationid;
1639						break;
1640					case OPERATION_TYPE_HOST_ADD:
1641					case OPERATION_TYPE_HOST_REMOVE:
1642					case OPERATION_TYPE_HOST_ENABLE:
1643					case OPERATION_TYPE_HOST_DISABLE:
1644						break;
1645					case OPERATION_TYPE_HOST_INVENTORY:
1646						$opinventory[] = $operationid;
1647						break;
1648				}
1649			}
1650
1651			// get OPERATION_TYPE_MESSAGE data
1652			if ($opmessage) {
1653				if ($this->outputIsRequested('opmessage', $options['selectOperations'])) {
1654					foreach ($opmessage as $operationId) {
1655						$operations[$operationId]['opmessage'] = [];
1656					}
1657
1658					$dbOpmessages = DBselect(
1659						'SELECT o.operationid,o.default_msg,o.subject,o.message,o.mediatypeid'.
1660							' FROM opmessage o'.
1661							' WHERE '.dbConditionInt('operationid', $opmessage)
1662					);
1663					while ($dbOpmessage = DBfetch($dbOpmessages)) {
1664						$operations[$dbOpmessage['operationid']]['opmessage'] = $dbOpmessage;
1665					}
1666				}
1667
1668				if ($this->outputIsRequested('opmessage_grp', $options['selectOperations'])) {
1669					foreach ($opmessage as $operationId) {
1670						$operations[$operationId]['opmessage_grp'] = [];
1671					}
1672
1673					$dbOpmessageGrp = DBselect(
1674						'SELECT og.operationid,og.usrgrpid'.
1675							' FROM opmessage_grp og'.
1676							' WHERE '.dbConditionInt('operationid', $opmessage)
1677					);
1678					while ($opmessageGrp = DBfetch($dbOpmessageGrp)) {
1679						$operations[$opmessageGrp['operationid']]['opmessage_grp'][] = $opmessageGrp;
1680					}
1681				}
1682
1683				if ($this->outputIsRequested('opmessage_usr', $options['selectOperations'])) {
1684					foreach ($opmessage as $operationId) {
1685						$operations[$operationId]['opmessage_usr'] = [];
1686					}
1687
1688					$dbOpmessageUsr = DBselect(
1689						'SELECT ou.operationid,ou.userid'.
1690							' FROM opmessage_usr ou'.
1691							' WHERE '.dbConditionInt('operationid', $opmessage)
1692					);
1693					while ($opmessageUsr = DBfetch($dbOpmessageUsr)) {
1694						$operations[$opmessageUsr['operationid']]['opmessage_usr'][] = $opmessageUsr;
1695					}
1696				}
1697			}
1698
1699			// get OPERATION_TYPE_COMMAND data
1700			if ($opcommand) {
1701				if ($this->outputIsRequested('opcommand', $options['selectOperations'])) {
1702					foreach ($opcommand as $operationId) {
1703						$operations[$operationId]['opcommand'] = [];
1704					}
1705
1706					$dbOpcommands = DBselect(
1707						'SELECT o.*'.
1708							' FROM opcommand o'.
1709							' WHERE '.dbConditionInt('operationid', $opcommand)
1710					);
1711					while ($dbOpcommand = DBfetch($dbOpcommands)) {
1712						$operations[$dbOpcommand['operationid']]['opcommand'] = $dbOpcommand;
1713					}
1714				}
1715
1716				if ($this->outputIsRequested('opcommand_hst', $options['selectOperations'])) {
1717					foreach ($opcommand as $operationId) {
1718						$operations[$operationId]['opcommand_hst'] = [];
1719					}
1720
1721					$dbOpcommandHst = DBselect(
1722						'SELECT oh.opcommand_hstid,oh.operationid,oh.hostid'.
1723							' FROM opcommand_hst oh'.
1724							' WHERE '.dbConditionInt('operationid', $opcommand)
1725					);
1726					while ($opcommandHst = DBfetch($dbOpcommandHst)) {
1727						$operations[$opcommandHst['operationid']]['opcommand_hst'][] = $opcommandHst;
1728					}
1729				}
1730
1731				if ($this->outputIsRequested('opcommand_grp', $options['selectOperations'])) {
1732					foreach ($opcommand as $operationId) {
1733						$operations[$operationId]['opcommand_grp'] = [];
1734					}
1735
1736					$dbOpcommandGrp = DBselect(
1737						'SELECT og.opcommand_grpid,og.operationid,og.groupid'.
1738							' FROM opcommand_grp og'.
1739							' WHERE '.dbConditionInt('operationid', $opcommand)
1740					);
1741					while ($opcommandGrp = DBfetch($dbOpcommandGrp)) {
1742						$operations[$opcommandGrp['operationid']]['opcommand_grp'][] = $opcommandGrp;
1743					}
1744				}
1745			}
1746
1747			// get OPERATION_TYPE_GROUP_ADD, OPERATION_TYPE_GROUP_REMOVE data
1748			if ($opgroup) {
1749				if ($this->outputIsRequested('opgroup', $options['selectOperations'])) {
1750					foreach ($opgroup as $operationId) {
1751						$operations[$operationId]['opgroup'] = [];
1752					}
1753
1754					$dbOpgroup = DBselect(
1755						'SELECT o.operationid,o.groupid'.
1756							' FROM opgroup o'.
1757							' WHERE '.dbConditionInt('operationid', $opgroup)
1758					);
1759					while ($opgroup = DBfetch($dbOpgroup)) {
1760						$operations[$opgroup['operationid']]['opgroup'][] = $opgroup;
1761					}
1762				}
1763			}
1764
1765			// get OPERATION_TYPE_TEMPLATE_ADD, OPERATION_TYPE_TEMPLATE_REMOVE data
1766			if ($optemplate) {
1767				if ($this->outputIsRequested('optemplate', $options['selectOperations'])) {
1768					foreach ($optemplate as $operationId) {
1769						$operations[$operationId]['optemplate'] = [];
1770					}
1771
1772					$dbOptemplate = DBselect(
1773						'SELECT o.operationid,o.templateid'.
1774							' FROM optemplate o'.
1775							' WHERE '.dbConditionInt('operationid', $optemplate)
1776					);
1777					while ($optemplate = DBfetch($dbOptemplate)) {
1778						$operations[$optemplate['operationid']]['optemplate'][] = $optemplate;
1779					}
1780				}
1781			}
1782
1783			// get OPERATION_TYPE_HOST_INVENTORY data
1784			if ($opinventory) {
1785				if ($this->outputIsRequested('opinventory', $options['selectOperations'])) {
1786					foreach ($opinventory as $operationId) {
1787						$operations[$operationId]['opinventory'] = [];
1788					}
1789
1790					$dbOpinventory = DBselect(
1791						'SELECT o.operationid,o.inventory_mode'.
1792							' FROM opinventory o'.
1793							' WHERE '.dbConditionInt('operationid', $opinventory)
1794					);
1795					while ($opinventory = DBfetch($dbOpinventory)) {
1796						$operations[$opinventory['operationid']]['opinventory'] = $opinventory;
1797					}
1798				}
1799			}
1800
1801			$operations = $this->unsetExtraFields($operations, ['operationid', 'actionid' ,'operationtype'],
1802				$options['selectOperations']
1803			);
1804			$result = $relationMap->mapMany($result, $operations, 'operations');
1805		}
1806
1807		return $result;
1808	}
1809
1810	/**
1811	 * Returns the parameters for creating a discovery rule filter validator.
1812	 *
1813	 * @return array
1814	 */
1815	protected function getFilterSchema() {
1816		return [
1817			'validators' => [
1818				'evaltype' => new CLimitedSetValidator([
1819					'values' => [
1820						CONDITION_EVAL_TYPE_OR,
1821						CONDITION_EVAL_TYPE_AND,
1822						CONDITION_EVAL_TYPE_AND_OR,
1823						CONDITION_EVAL_TYPE_EXPRESSION
1824					],
1825					'messageInvalid' => _('Incorrect type of calculation for action "%1$s".')
1826				]),
1827				'formula' => new CStringValidator([
1828					'empty' => true
1829				]),
1830				'conditions' => new CCollectionValidator([
1831					'empty' => true,
1832					'messageInvalid' => _('Incorrect conditions for action "%1$s".')
1833				])
1834			],
1835			'postValidators' => [
1836				new CConditionValidator([
1837					'messageInvalidFormula' => _('Incorrect custom expression "%2$s" for action "%1$s": %3$s.'),
1838					'messageMissingCondition' => _('Condition "%2$s" used in formula "%3$s" for action "%1$s" is not defined.'),
1839					'messageUnusedCondition' => _('Condition "%2$s" is not used in formula "%3$s" for action "%1$s".'),
1840					'messageAndWithSeveralTriggers' => _('Comparing several triggers with "and" is not allowed.')
1841				])
1842			],
1843			'required' => ['evaltype', 'conditions'],
1844			'messageRequired' => _('No "%2$s" given for the filter of action "%1$s".'),
1845			'messageUnsupported' => _('Unsupported parameter "%2$s" for the filter of action "%1$s".')
1846		];
1847	}
1848
1849	/**
1850	 * Returns the parameters for creating a action filter condition validator.
1851	 *
1852	 * @return array
1853	 */
1854	protected function getFilterConditionSchema() {
1855		$conditionTypes = [
1856			CONDITION_TYPE_HOST_GROUP, CONDITION_TYPE_HOST, CONDITION_TYPE_TRIGGER, CONDITION_TYPE_TRIGGER_NAME,
1857			CONDITION_TYPE_TRIGGER_SEVERITY, CONDITION_TYPE_TRIGGER_VALUE, CONDITION_TYPE_TIME_PERIOD,
1858			CONDITION_TYPE_DHOST_IP, CONDITION_TYPE_DSERVICE_TYPE, CONDITION_TYPE_DSERVICE_PORT,
1859			CONDITION_TYPE_DSTATUS, CONDITION_TYPE_DUPTIME, CONDITION_TYPE_DVALUE, CONDITION_TYPE_TEMPLATE,
1860			CONDITION_TYPE_EVENT_ACKNOWLEDGED, CONDITION_TYPE_APPLICATION, CONDITION_TYPE_MAINTENANCE,
1861			CONDITION_TYPE_DRULE, CONDITION_TYPE_DCHECK, CONDITION_TYPE_PROXY, CONDITION_TYPE_DOBJECT,
1862			CONDITION_TYPE_HOST_NAME, CONDITION_TYPE_EVENT_TYPE, CONDITION_TYPE_HOST_METADATA
1863		];
1864
1865		$operators = [
1866			CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL, CONDITION_OPERATOR_LIKE,
1867			CONDITION_OPERATOR_NOT_LIKE, CONDITION_OPERATOR_IN, CONDITION_OPERATOR_MORE_EQUAL,
1868			CONDITION_OPERATOR_LESS_EQUAL, CONDITION_OPERATOR_NOT_IN
1869		];
1870
1871		return [
1872			'validators' => [
1873				'conditiontype' => new CLimitedSetValidator([
1874					'values' => $conditionTypes,
1875					'messageInvalid' => _('Incorrect filter condition type for action "%1$s".')
1876				]) ,
1877				'value' => new CStringValidator([
1878					'empty' => true
1879				]),
1880				'formulaid' => new CStringValidator([
1881					'regex' => '/[A-Z]+/',
1882					'messageEmpty' => _('Empty filter condition formula ID for action "%1$s".'),
1883					'messageRegex' => _('Incorrect filter condition formula ID for action "%1$s".')
1884				]),
1885				'operator' => new CLimitedSetValidator([
1886					'values' => $operators,
1887					'messageInvalid' => _('Incorrect filter condition operator for action "%1$s".')
1888				])
1889			],
1890			'required' => ['conditiontype', 'value'],
1891			'postValidators' => [
1892				new CActionCondValidator()
1893			],
1894			'messageRequired' => _('No "%2$s" given for a filter condition of action "%1$s".'),
1895			'messageUnsupported' => _('Unsupported parameter "%2$s" for a filter condition of action "%1$s".')
1896		];
1897	}
1898
1899	protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) {
1900		$sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts);
1901
1902		if ($options['countOutput'] === null) {
1903			// add filter fields
1904			if ($this->outputIsRequested('formula', $options['selectFilter'])
1905				|| $this->outputIsRequested('eval_formula', $options['selectFilter'])
1906				|| $this->outputIsRequested('conditions', $options['selectFilter'])) {
1907
1908				$sqlParts = $this->addQuerySelect('a.formula', $sqlParts);
1909				$sqlParts = $this->addQuerySelect('a.evaltype', $sqlParts);
1910			}
1911			if ($this->outputIsRequested('evaltype', $options['selectFilter'])) {
1912				$sqlParts = $this->addQuerySelect('a.evaltype', $sqlParts);
1913			}
1914		}
1915
1916		return $sqlParts;
1917	}
1918
1919	/**
1920	 * Converts a formula with letters to a formula with IDs and updates it.
1921	 *
1922	 * @param string 	$actionId
1923	 * @param string 	$formulaWithLetters		formula with letters
1924	 * @param array 	$conditions
1925	 */
1926	protected function updateFormula($actionId, $formulaWithLetters, array $conditions) {
1927		$formulaIdToConditionId = [];
1928
1929		foreach ($conditions as $condition) {
1930			$formulaIdToConditionId[$condition['formulaid']] = $condition['conditionid'];
1931		}
1932		$formula = CConditionHelper::replaceLetterIds($formulaWithLetters, $formulaIdToConditionId);
1933
1934		DB::updateByPk('actions', $actionId, ['formula' => $formula]);
1935	}
1936
1937	/**
1938	 * Validate input given to action.create API call.
1939	 *
1940	 * @param $actions
1941	 */
1942	protected function validateCreate($actions) {
1943		$actionDbFields = [
1944			'name'        => null,
1945			'eventsource' => null
1946		];
1947
1948		$duplicates = [];
1949		foreach ($actions as $action) {
1950			if (!check_db_fields($actionDbFields, $action)) {
1951				self::exception(
1952					ZBX_API_ERROR_PARAMETERS,
1953					_s('Incorrect parameter for action "%1$s".', $action['name'])
1954				);
1955			}
1956			if (isset($action['esc_period']) && $action['esc_period'] < SEC_PER_MIN
1957					&& $action['eventsource'] == EVENT_SOURCE_TRIGGERS) {
1958
1959				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1960					'Action "%1$s" has incorrect value for "esc_period" (minimum %2$s seconds).',
1961					$action['name'], SEC_PER_MIN
1962				));
1963			}
1964			if (isset($duplicates[$action['name']])) {
1965				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Action "%1$s" already exists.', $action['name']));
1966			}
1967			else {
1968				$duplicates[$action['name']] = $action['name'];
1969			}
1970		}
1971
1972		$dbActionsWithSameName = $this->get([
1973			'filter' => ['name' => $duplicates],
1974			'output' => API_OUTPUT_EXTEND,
1975			'editable' => true,
1976			'nopermissions' => true
1977		]);
1978		if ($dbActionsWithSameName) {
1979			$dbActionWithSameName = reset($dbActionsWithSameName);
1980			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Action "%1$s" already exists.', $dbActionWithSameName['name']));
1981		}
1982
1983		$filterValidator = new CSchemaValidator($this->getFilterSchema());
1984		$filterConditionValidator = new CSchemaValidator($this->getFilterConditionSchema());
1985
1986		$conditionsToValidate = [];
1987		$operationsToValidate = [];
1988
1989		// Validate "filter" sections and "conditions" in them, ensure that "operations" section
1990		// is present and is not empty. Also collect conditions and operations for more validation.
1991		foreach ($actions as $action) {
1992			if (isset($action['filter'])) {
1993				$filterValidator->setObjectName($action['name']);
1994				$this->checkValidator($action['filter'], $filterValidator);
1995
1996				foreach ($action['filter']['conditions'] as $condition) {
1997					$filterConditionValidator->setObjectName($action['name']);
1998					$this->checkValidator($condition, $filterConditionValidator);
1999					$conditionsToValidate[] = $condition;
2000				}
2001			}
2002
2003			if (!isset($action['operations']) || empty($action['operations'])) {
2004				self::exception(
2005					ZBX_API_ERROR_PARAMETERS,
2006					_s('Incorrect parameter for action "%1$s".', $action['name'])
2007				);
2008			}
2009			else {
2010				foreach ($action['operations'] as $operation) {
2011					if (array_key_exists('operationid', $operation)) {
2012						self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect input parameters.'));
2013					}
2014
2015					$operationsToValidate[] = $operation;
2016				}
2017			}
2018		}
2019
2020		// Validate conditions and operations in regard to whats in database now.
2021		if ($conditionsToValidate) {
2022			$this->validateConditionsPermissions($conditionsToValidate);
2023		}
2024		$this->validateOperationsIntegrity($operationsToValidate);
2025	}
2026
2027	/**
2028	 * Validate input given to action.update API call.
2029	 *
2030	 * @param array $actions
2031	 * @param array $actionsDb
2032	 *
2033	 * @internal param array $actionDb
2034	 */
2035	protected function validateUpdate($actions, $actionsDb) {
2036		foreach ($actions as $action) {
2037			if (isset($action['actionid']) && !isset($actionsDb[$action['actionid']])) {
2038				self::exception(
2039					ZBX_API_ERROR_PERMISSIONS,
2040					_('No permissions to referred object or it does not exist!')
2041				);
2042			}
2043		}
2044		$actions = zbx_toHash($actions, 'actionid');
2045
2046		// check fields
2047		$duplicates = [];
2048		foreach ($actions as $action) {
2049			$actionName = isset($action['name']) ? $action['name'] : $actionsDb[$action['actionid']]['name'];
2050
2051			if (!check_db_fields(['actionid' => null], $action)) {
2052				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
2053					'Incorrect parameters for action update method "%1$s".', $actionName
2054				));
2055			}
2056
2057			// check if user changed esc_period for trigger eventsource
2058			if (isset($action['esc_period'])
2059					&& $action['esc_period'] < SEC_PER_MIN
2060					&& $actionsDb[$action['actionid']]['eventsource'] == EVENT_SOURCE_TRIGGERS) {
2061
2062				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
2063					'Action "%1$s" has incorrect value for "esc_period" (minimum %2$s seconds).',
2064					$actionName, SEC_PER_MIN
2065				));
2066			}
2067
2068			$this->checkNoParameters(
2069				$action,
2070				['eventsource'],
2071				_('Cannot update "%1$s" for action "%2$s".'),
2072				$actionName
2073			);
2074
2075			if (!isset($action['name'])) {
2076				continue;
2077			}
2078
2079			if (isset($duplicates[$action['name']])) {
2080				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Action "%1$s" already exists.', $action['name']));
2081			}
2082			else {
2083				$duplicates[$action['name']] = $action['name'];
2084			}
2085		}
2086
2087		// Unset accidentally passed in "evaltype" and "formula" fields.
2088		foreach ($actions as &$action) {
2089			unset($action['evaltype'], $action['formula']);
2090		}
2091		unset($action);
2092
2093		$filterValidator = new CSchemaValidator($this->getFilterSchema());
2094
2095		$filterConditionValidator = new CSchemaValidator($this->getFilterConditionSchema());
2096
2097		$operationsToValidate = [];
2098		$conditionsToValidate = [];
2099
2100		foreach ($actions as $actionId => $action) {
2101			$actionDb = $actionsDb[$actionId];
2102
2103			if (isset($action['name'])) {
2104				$actionName = $action['name'];
2105
2106				$actionExists = $this->get([
2107					'filter' => ['name' => $actionName],
2108					'output' => ['actionid'],
2109					'editable' => true,
2110					'nopermissions' => true,
2111					'preservekeys' => true
2112				]);
2113				if (($actionExists = reset($actionExists))
2114					&& (bccomp($actionExists['actionid'], $actionId) != 0)
2115				) {
2116					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Action "%1$s" already exists.', $actionName));
2117				}
2118			}
2119			else {
2120				$actionName = $actionDb['name'];
2121			}
2122
2123			if (isset($action['filter'])) {
2124				$actionFilter = $action['filter'];
2125
2126				$filterValidator->setObjectName($actionName);
2127				$filterConditionValidator->setObjectName($actionName);
2128
2129				$this->checkValidator($actionFilter, $filterValidator);
2130
2131				foreach ($actionFilter['conditions'] as $condition) {
2132					$this->checkValidator($condition, $filterConditionValidator);
2133					$conditionsToValidate[] = $condition;
2134				}
2135			}
2136
2137			if (isset($action['operations']) && empty($action['operations'])) {
2138				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Action "%1$s" no operations defined.', $actionName));
2139			}
2140			elseif (isset($action['operations'])) {
2141				$operationsDb = $actionsDb[$action['actionid']]['operations'];
2142				$operationsDb = zbx_toHash($operationsDb, 'operationid');
2143				foreach ($action['operations'] as $operation) {
2144					if (!isset($operation['operationid'])) {
2145						$operationsToValidate[] = $operation;
2146					}
2147					elseif (isset($operationsDb[$operation['operationid']])) {
2148						$operationsToValidate[] = $operation;
2149					}
2150					else {
2151						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action operationid.'));
2152					}
2153				}
2154			}
2155		}
2156
2157		if ($conditionsToValidate) {
2158			$this->validateConditionsPermissions($conditionsToValidate);
2159		}
2160		if ($operationsToValidate) {
2161			$this->validateOperationsIntegrity($operationsToValidate);
2162		}
2163	}
2164
2165	/**
2166	 * Check permissions to DB entities referenced by action conditions.
2167	 *
2168	 * @param array $conditions   conditions for which permissions to referenced DB entities will be checked
2169	 */
2170	protected function validateConditionsPermissions(array $conditions) {
2171		$hostGroupIdsAll = [];
2172		$templateIdsAll = [];
2173		$triggerIdsAll = [];
2174		$hostIdsAll = [];
2175		$discoveryRuleIdsAll = [];
2176		$discoveryCheckIdsAll = [];
2177		$proxyIdsAll = [];
2178
2179		foreach ($conditions as $condition) {
2180			$conditionValue = $condition['value'];
2181			// validate condition values depending on condition type
2182			switch ($condition['conditiontype']) {
2183				case CONDITION_TYPE_HOST_GROUP:
2184					$hostGroupIdsAll[$conditionValue] = $conditionValue;
2185					break;
2186
2187				case CONDITION_TYPE_TEMPLATE:
2188					$templateIdsAll[$conditionValue] = $conditionValue;
2189					break;
2190
2191				case CONDITION_TYPE_TRIGGER:
2192					$triggerIdsAll[$conditionValue] = $conditionValue;
2193					break;
2194
2195				case CONDITION_TYPE_HOST:
2196					$hostIdsAll[$conditionValue] = $conditionValue;
2197					break;
2198
2199				case CONDITION_TYPE_DRULE:
2200					$discoveryRuleIdsAll[$conditionValue] = $conditionValue;
2201					break;
2202
2203				case CONDITION_TYPE_DCHECK:
2204					$discoveryCheckIdsAll[$conditionValue] = $conditionValue;
2205					break;
2206
2207				case CONDITION_TYPE_PROXY:
2208					$proxyIdsAll[$conditionValue] = $conditionValue;
2209					break;
2210			}
2211		}
2212
2213		if (!API::HostGroup()->isWritable($hostGroupIdsAll)) {
2214			self::exception(
2215				ZBX_API_ERROR_PARAMETERS,
2216				_('Incorrect action condition host group. Host group does not exist or you have no access to it.')
2217			);
2218		}
2219		if (!API::Host()->isWritable($hostIdsAll)) {
2220			self::exception(
2221				ZBX_API_ERROR_PARAMETERS,
2222				_('Incorrect action condition host. Host does not exist or you have no access to it.')
2223			);
2224		}
2225		if (!API::Template()->isWritable($templateIdsAll)) {
2226			self::exception(
2227				ZBX_API_ERROR_PARAMETERS,
2228				_('Incorrect action condition template. Template does not exist or you have no access to it.')
2229			);
2230		}
2231		if (!API::Trigger()->isWritable($triggerIdsAll)) {
2232			self::exception(
2233				ZBX_API_ERROR_PARAMETERS,
2234				_('Incorrect action condition trigger. Trigger does not exist or you have no access to it.')
2235			);
2236		}
2237		if (!API::DRule()->isWritable($discoveryRuleIdsAll)) {
2238			self::exception(
2239				ZBX_API_ERROR_PARAMETERS,
2240				_('Incorrect action condition discovery rule. Discovery rule does not exist or you have no access to it.')
2241			);
2242		}
2243		if (!API::DCheck()->isWritable($discoveryCheckIdsAll)) {
2244			self::exception(
2245				ZBX_API_ERROR_PARAMETERS,
2246				_('Incorrect action condition discovery check. Discovery check does not exist or you have no access to it.')
2247			);
2248		}
2249		if (!API::Proxy()->isWritable($proxyIdsAll)) {
2250			self::exception(
2251				ZBX_API_ERROR_PARAMETERS,
2252				_('Incorrect action condition proxy. Proxy does not exist or you have no access to it.')
2253			);
2254		}
2255	}
2256}
2257